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.
  • Anahtarların bölümlerine kullanıcı erişimi: Bunu yapmak genellikle uyumluluk hatalarına neden olur.

Aslında, bu ilkelerin ihlal edilmesini 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 kümesindeki her bir anahtar için dahili olarak serializeKey yöntemini çağırır ve verilen secretKeyAccess öğesini 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 kısımlarına 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 bir şekilde belirtilmeyen farklı biçimlere dönüştürmek gerekir. Bu genellikle, anahtar kullanıldığında meta verilerin sağlanması gerektiği anlamına gelir. Başka bir deyişle, anahtarın her zaman aynı algoritmayla kullanıldığı varsayıldığında, böyle bir anahtar dolaylı olarak aynı meta veriye sahip olmaya devam eder ve 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şmiyorsa dönüştürme işlemi başarısız olmalıdır.

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 kullanır; C++ ve Golang'da, 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 bir sorunlu. 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 aktarımında parametrelerin beklediğiniz gibi olduğunu 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, verifyEcdsaSignature değerini ilk bağımsız değişkenin EcdsaPublicKey olacağı şekilde 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. Bir EcdsaPublicKey yerine KeysetHandle iletmek daha iyidir. Kodu, herhangi bir ek çalışma yapmadan 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: Anahtar materyali diskten veya ağdan ilk kez okunurken 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.