به صورت برنامه‌ریزی مواد کلیدی را صادر کنید

تینک از اعمال بد مربوط به کلیدها جلوگیری می کند، مانند:

  • دسترسی کاربر به مواد کلید مخفی – در عوض، کلیدهای مخفی باید هر زمان که امکان دارد با استفاده از یکی از روش های از پیش تعریف شده ای که Tink از چنین سیستم هایی پشتیبانی می کند، در یک KMS ذخیره شود.
  • دسترسی کاربر به بخش‌هایی از کلیدها – انجام این کار اغلب منجر به اشکالات سازگاری می‌شود.

در واقع مواردی وجود دارد که مستلزم نقض این اصول است. تینک مکانیسم هایی را برای انجام ایمن ارائه می دهد که در بخش های زیر توضیح داده شده است.

رمزهای دسترسی کلید مخفی

برای دسترسی به مواد کلید مخفی، کاربران باید یک نشانه داشته باشند (که معمولاً یک شی از یک کلاس است، بدون هیچ گونه عملکردی). توکن معمولاً توسط روشی مانند InsecureSecretKeyAccess.get() ارائه می شود. در Google، کاربران از استفاده از این عملکرد با استفاده از قابلیت مشاهده Bazel BUILD منع می‌شوند. در خارج از Google، بازبینان امنیتی می توانند پایگاه کد خود را برای استفاده از این عملکرد جستجو کنند.

یکی از ویژگی های مفید این توکن ها این است که می توان آنها را منتقل کرد. به عنوان مثال، فرض کنید تابعی دارید که یک کلید دلخواه Tink را سریال می کند:

String serializeKey(Key key, @Nullable SecretKeyAccess secretKeyAccess);

برای کلیدهایی که مواد کلید مخفی دارند، این تابع نیاز دارد که شی secretKeyAccess غیر تهی باشد و یک رمز واقعی SecretKeyAccess ذخیره شده باشد. برای کلیدهایی که هیچ ماده محرمانه ای ندارند، secretKeyAccess نادیده گرفته می شود.

با توجه به چنین تابعی، می توان تابعی را نوشت که کل مجموعه کلید را سریال سازی کند:

String serializeKeyset(KeysetHandle keyset, @Nullable SecretKeyAccess
secretKeyAccess);

این تابع برای هر کلید در مجموعه کلید، serializeKey به صورت داخلی فراخوانی می‌کند و secretKeyAccess داده شده را به تابع زیرین ارسال می‌کند. کاربرانی که سپس serializeKeyset بدون نیاز به سریال سازی مواد کلید مخفی فراخوانی می کنند، می توانند null به عنوان آرگومان دوم استفاده کنند. کاربرانی که نیاز به سریال سازی مواد کلید مخفی دارند باید از InsecureSecretKeyAccess.get() استفاده کنند.

دسترسی به قطعات یک کلید

کلیدهای Tink نه تنها حاوی مواد کلید خام هستند، بلکه همچنین متادیتا هستند که نحوه استفاده از کلید را مشخص می کند (و به نوبه خود، نباید از آن به روش دیگری استفاده شود). برای مثال، یک کلید RSA SSA PSS در Tink مشخص می‌کند که این کلید RSA فقط می‌تواند با الگوریتم امضای PSS با استفاده از تابع هش مشخص و طول نمک مشخص استفاده شود.

گاهی اوقات، لازم است یک کلید Tink را به فرمت های مختلف تبدیل کنید که ممکن است به صراحت تمام این ابرداده را مشخص نکند. این معمولاً به این معنی است که هنگام استفاده از کلید باید ابرداده ارائه شود. به عبارت دیگر، با فرض اینکه کلید همیشه با یک الگوریتم استفاده می شود، چنین کلیدی همچنان به طور ضمنی همان ابرداده را دارد، فقط در مکان دیگری ذخیره می شود.

هنگامی که یک کلید Tink را به فرمت دیگری تبدیل می کنید، باید مطمئن شوید که ابرداده کلید Tink با فراداده (به طور ضمنی مشخص شده) فرمت کلید دیگر مطابقت دارد. اگر مطابقت نداشته باشد، تبدیل باید شکست بخورد.

از آنجایی که این بررسی‌ها اغلب گم یا ناقص هستند، Tink دسترسی به APIهایی را محدود می‌کند که به مواد کلیدی دسترسی دارند که فقط جزئی است، اما ممکن است به عنوان یک کلید کامل اشتباه گرفته شود. در جاوا، Tink برای این کار از RestrictedApi استفاده می‌کند، در C++ و Golang از توکن‌هایی شبیه به نشانه‌های دسترسی کلید مخفی استفاده می‌کند.

کاربران این APIها مسئول جلوگیری از حملات استفاده مجدد کلیدی و ناسازگاری هستند.

معمولاً با روش‌هایی مواجه می‌شوید که «دسترسی جزئی کلید» را در زمینه صادرات یا وارد کردن کلیدها به Tink محدود می‌کنند. بهترین شیوه های زیر نحوه عملکرد ایمن در این سناریوها را توضیح می دهد.

بهترین روش: تمام پارامترها را در صادرات کلید بررسی کنید

به عنوان مثال، اگر تابعی بنویسید که کلید عمومی HPKE را صادر می کند:

روش بد برای صادر کردن کلید عمومی:

/** Provide the key to our users which don't have Tink. */
byte[] exportTinkHpkeKey(HpkePublicKey key) {
    return key.getPublicKeyBytes().toByteArray();
}

این مشکل ساز است. پس از دریافت کلید، شخص ثالثی که از آن استفاده می کند برخی از فرضیات را در مورد پارامترهای کلید ایجاد می کند: به عنوان مثال، فرض می کند که الگوریتم HPKE AEAD استفاده شده برای این کلید 256 بیتی AES-GCM است.

توصیه: بررسی کنید که پارامترها همان چیزی است که در صادرات کلید انتظار دارید.

راه بهتر برای صادرات کلید عمومی:

/** Provide the key to our users which don't have Tink. */
byte[] exportTinkHpkeKeyForOurUsers(HpkePublicKey key) {
    // Our users assume we use KEM_P256_HKDF_SHA256 for the KEM.
    if (!key.getParameters().getKemId().equals(HpkeParameters.KemId.KEM_P256_HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we use HKDF SHA256 to create the key material.
    if (!key.getParameters().getKdfId().equals(HpkeParameters.KdfId.HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume that we use AES GCM with 256 bit keys.
    if (!key.getParameters().getAeadId().equals(HpkeParameters.AeadId.AES_256_GCM)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we follow the standard and don't add a Tink style prefix
    if (!key.getParameters().getVariant().equals(HpkeParameters.Variant.NO_PREFIX)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    return key.getPublicKeyBytes().toByteArray();
}

بهترین تمرین: از اشیاء Tink در اسرع وقت در وارد کردن کلید استفاده کنید

این خطر حملات سردرگمی کلید را به حداقل می رساند، زیرا شی Tink Key الگوریتم صحیح را به طور کامل مشخص می کند و تمام ابرداده ها را همراه با مواد کلیدی ذخیره می کند.

به مثال زیر توجه کنید:

استفاده بدون تایپ:

void verifyEcdsaSignature(ECPoint ecPoint, byte[] signature, byte[] message)
        throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    KeysetHandle handle = KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
    PublicKeyVerify publicKeyVerify = handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class);
    publicKeyVerify.verify(signature, message);
}

این مستعد خطا است: در سایت تماس بسیار آسان است فراموش کنید که هرگز نباید از همان ecPoint با الگوریتم دیگری استفاده کنید. به عنوان مثال، اگر یک تابع مشابه به نام encryptWithECHybridEncrypt وجود داشته باشد، تماس گیرنده ممکن است از همان نقطه منحنی برای رمزگذاری یک پیام استفاده کند که به راحتی می تواند منجر به آسیب پذیری شود.

در عوض، بهتر است verifyEcdsaSignature تغییر دهید تا اولین آرگومان EcdsaPublicKey باشد. در واقع، هر زمان که کلید از روی دیسک یا شبکه خوانده می‌شود، باید فوراً به یک شی EcdsaPublicKey تبدیل شود: در این مرحله شما از قبل می‌دانید که کلید در چه روشی استفاده می‌شود، بنابراین بهتر است به آن متعهد شوید.

کد قبلی را می توان حتی بیشتر بهبود بخشید. به جای پاس دادن در EcdsaPublicKey ، ارسال در KeysetHandle بهتر است. این کد را برای چرخش کلید بدون هیچ کار اضافی آماده می کند. بنابراین این باید ترجیح داده شود.

با این حال، بهبودها انجام نشده است: حتی بهتر است که در شی PublicKeyVerify عبور دهید: این برای این تابع کافی است، بنابراین ارسال در شی PublicKeyVerify به طور بالقوه مکان هایی را که می توان از این تابع استفاده کرد افزایش می دهد. با این حال، در این مرحله، تابع نسبتاً بی اهمیت می شود و می توان آن را خطی کرد.

توصیه: هنگامی که مطالب کلیدی برای اولین بار از دیسک یا شبکه خوانده می شود، اشیاء Tink مربوطه را در اسرع وقت ایجاد کنید.

استفاده تایپ شده:

KeysetHandle readEcdsaKeyFromFile(Path fileWithEcdsaKey) throws Exception {
    byte[] content = Files.readAllBytes(fileWithEcdsaKey);
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(content, 0, 32));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(content, 32, 64));
    ECPoint point = new ECPoint(x, y);
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    return KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
}

با استفاده از چنین کدی، آرایه بایت را بلافاصله پس از خواندن به یک شی Tink تبدیل می کنیم و به طور کامل مشخص می کنیم که از چه الگوریتمی استفاده شود. این رویکرد احتمال حملات سردرگمی کلیدی را به حداقل می رساند.