Anahtar Materyalini Programatik Olarak Dışa Aktarma

Tink, anahtarlarla ilgili aşağıdaki gibi kötü uygulamaları önler:

  • Kullanıcının gizli anahtar materyaline erişimi: Bunun yerine, gizli anahtarlar mümkün olduğunda Tink'in bu tür sistemleri desteklediği önceden tanımlanmış yöntemlerden biri kullanılarak bir KMS'de depolanmalıdır.
  • Kullanıcıların anahtarların belirli bölümlerine erişmesi: Bu durum genellikle uyumluluk hatalarına neden olur.

Gerçekte, bu ilkeleri ihlal etmeyi gerektiren durumlar vardır. Tink, bunu güvenli bir şekilde yapabilmeniz için aşağıdaki bölümlerde açıklanan mekanizmalar sağlar.

Gizli Anahtar Erişim Jetonları

Kullanıcıların gizli anahtar materyaline erişmek için bir jetonunun (genellikle herhangi bir işleve sahip olmayan bir sınıf nesnesi) olması gerekir. Jeton genellikle InsecureSecretKeyAccess.get() gibi bir yöntemle sağlanır. Google'da, Bazel BUILD görünürlüğü kullanılarak kullanıcıların bu işlevi kullanması engellenir. Google dışındaki güvenlik inceleme uzmanları, kod tabanlarında bu işlevin kullanımlarını arayabilir.

Bu jetonların faydalı özelliklerinden biri, başka kullanıcılara aktarılabilmesidir. Örneğin, rastgele bir Tink anahtarını seri hale getiren bir işleviniz olduğunu varsayalım:

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

Gizli anahtar materyali içeren anahtarlar için bu işlevin çalışması, secretKeyAccess nesnesinin boş değerli olmaması ve depolanan gerçek bir SecretKeyAccess jetonu olması gerekir. Gizli materyali olmayan anahtarlar için secretKeyAccess yoksayılır.

Bu tür bir işlev verildiğinde, tüm anahtar grubunu seri hale getiren bir işlev yazılabilir:

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

Bu işlev, anahtar grubundaki her anahtar için serializeKey işlevini dahili olarak çağırır ve belirtilen secretKeyAccess işlevini temel işleve iletir. Ardından, gizli anahtar materyalini serileştirmeye gerek kalmadan serializeKeyset işlevini çağıran kullanıcılar ikinci bağımsız değişken olarak null kullanabilir. Gizli anahtar materyalini serileştirmesi gereken kullanıcılar InsecureSecretKeyAccess.get() kullanmalıdır.

Anahtarın bölümlerine erişim

Tink anahtarları yalnızca ham anahtar materyalini değil, anahtarın nasıl kullanılacağını (ve dolayısıyla başka bir şekilde kullanılmaması gerektiğini) belirten meta verileri de içerir. Örneğin, Tink'teki bir RSA SSA PSS anahtarı, bu RSA anahtarının yalnızca belirtilen karma işlevi ve belirtilen takviye uzunluğu kullanılarak PSS imza algoritmasıyla kullanılabileceğini belirtir.

Bazen bir Tink anahtarını, tüm bu meta verileri açıkça belirtmeyen farklı biçimlere dönüştürmeniz gerekir. Bu genellikle, anahtar kullanıldığında meta verilerin sağlanması gerektiği anlamına gelir. Diğer bir deyişle, anahtarın her zaman aynı algoritmayla kullanıldığı varsayıldığında, bu tür bir anahtar hâlâ dolaylı olarak aynı meta verilere sahiptir, yalnızca farklı bir yerde depolanır.

Bir Tink anahtarını farklı bir biçime dönüştürdüğünüzde, Tink anahtarının meta verilerinin diğer anahtar biçiminin (dolaylı olarak belirtilen) meta verileriyle eşleştiğinden emin olmanız gerekir. Eşleşmezse dönüşüm başarısız olur.

Bu kontroller genellikle eksik olduğundan Tink, yalnızca kısmi olan ancak tam anahtar olarak yanlış anlaşılabilecek anahtar materyaline erişim sağlayan API'lere erişimi kısıtlar. Java'da Tink bunun için RestrictedApi'yi, C++ ve Golang'da ise gizli anahtar erişim jetonlarına benzer jetonları kullanır.

Bu API'lerin kullanıcıları, hem temel yeniden kullanım saldırılarını hem de uyumsuzlukları önlemekten sorumludur.

En sık olarak, Tink'ten anahtar dışa aktarma veya Tink'e anahtar içe aktarma bağlamında "kısmi anahtar erişimini" kısıtlayan yöntemlerle karşılaşırsınız. Aşağıdaki en iyi uygulamalarda, bu senaryolarda nasıl güvenli bir şekilde çalışacağınız açıklanmaktadır.

En İyi Uygulama: Anahtar dışa aktarma işleminde tüm parametreleri doğrulayın

Örneğin, HPKE ortak anahtarını dışa aktaran bir işlev yazarsanız:

Ortak anahtarı dışa aktarmanın kötü yolu:

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

Bu sorunlu bir durumdur. Anahtarı alan üçüncü taraf, anahtarın parametreleri hakkında bazı varsayımlarda bulunur: Örneğin, bu 256 bitlik anahtar için kullanılan HPKE AEAD algoritmasının AES-GCM olduğunu varsayar.

Öneri: Anahtar dışa aktarma işleminde parametrelerin beklediğiniz gibi olup olmadığını doğrulayın.

Ortak anahtarı dışa aktarmanın daha iyi bir yolu:

/** 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();
}

En İyi Uygulama: Anahtar içe aktarılırken Tink nesnelerini mümkün olduğunca erken kullanın

Bu, Tink Anahtarı nesnesi tam olarak doğru algoritmayı belirttiği ve tüm meta verileri anahtar materyaliyle birlikte depoladığı için önemli karışıklık saldırısı riskini en aza indirir.

Aşağıdaki örneği inceleyin:

Yazılı olmayan kullanım:

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);
}

Bu, hatalara yol açabilir: Arama sitesinde, aynı ecPoint değerini başka bir algoritmayla kullanmamanız gerektiğini unutmanız çok kolaydır. Örneğin, encryptWithECHybridEncrypt adında benzer bir işlev varsa çağrıyı yapan kişi bir mesajı şifrelemek için aynı eğri noktasını kullanabilir. Bu da kolayca güvenlik açıklarına yol açabilir.

Bunun yerine, ilk bağımsız değişken EcdsaPublicKey olacak şekilde verifyEcdsaSignature değerini değiştirmek daha iyidir. Aslında anahtar diskten veya ağdan her okunduğunda hemen bir EcdsaPublicKey nesnesine dönüştürülmelidir: Bu noktada anahtarın hangi şekilde kullanıldığını zaten biliyorsunuzdur, bu nedenle anahtarı bağlamak en iyisidir.

Önceki kod daha da iyileştirilebilir. EcdsaPublicKey yerine KeysetHandle göndermek daha iyidir. Ek çalışma gerektirmeden kodu anahtar rotasyonu için hazırlar. Dolayısıyla bu tercih edilir.

İyileştirmeler bununla sınırlı değil: PublicKeyVerify nesnesini iletmek daha da iyidir. Bu işlev için yeterlidir. Bu nedenle, PublicKeyVerify nesnesini iletmek bu işlevin kullanılabileceği yerleri artırabilir. Ancak bu noktada işlev oldukça önemsiz hale gelir ve satır içine alınabilir.

Öneri: Ana materyal diskten veya ağdan ilk kez okunduğunda, ilgili Tink nesnelerini en kısa sürede oluşturun.

Yazılı kullanım:

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();
}

Bu tür bir kod kullanarak, bayt dizisi okunduğunda hemen Tink nesnesine dönüştürülür ve hangi algoritmanın kullanılması gerektiğini tam olarak belirtiriz. Bu yaklaşım, anahtar karışıklığı saldırılarının olasılığını en aza indirir.