Anahtar Materyalini Programatik Olarak Dışa Aktarma

Tink, anahtarlarla ilgili kötü uygulamalardan (ör. aşağıdakiler) kaçınılmasını önerir:

  • 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ı

Gizli anahtar materyaline erişmek için kullanıcıların bir jetona sahip olması gerekir (bu jeton genellikle işlev içermeyen bir sınıfın nesnesi olur). 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 seri hale getirmesi gereken kullanıcılar InsecureSecretKeyAccess.get() değerini 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 tuz 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 saklanı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 veya tamamlanmadığından 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 anahtar yeniden kullanma 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ımlar yapar: Ö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 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 iyi uygulama: Anahtar içe aktarma işleminde Tink nesnelerini mümkün olduğunca erken kullanın

Tink anahtar nesnesi doğru algoritmayı tam olarak belirttiği ve tüm meta verileri anahtar materyaliyle birlikte depoladığı için bu, anahtar karışıklığı saldırılarının 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 adlı benzer bir işlev varsa arayan, 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.

Yukarıdaki kod daha da iyileştirilebilir. EcdsaPublicKey yerine KeysetHandle göndermek daha iyidir. Ek çalışma gerektirmeden kodu anahtar rotasyonu için hazırlar. Bu nedenle bu yöntem tercih edilmelidir.

Ancak iyileş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, okunur okunmaz bayt dizisini hemen bir Tink nesnesine dönüştürür ve hangi algoritmanın kullanılacağını tam olarak belirtiriz. Bu yaklaşım, anahtar karışıklığı saldırılarının olasılığını en aza indirir.