प्रोग्राम के हिसाब से, पासकोड एक्सपोर्ट करना

Tink, पासकोड से जुड़े गलत तरीकों का इस्तेमाल करने से रोकता है. जैसे:

  • उपयोगकर्ता के पास पासकोड का ऐक्सेस होना – इसके बजाय, जब भी संभव हो, पासकोड को केएमएस में सेव किया जाना चाहिए. इसके लिए, पहले से तय किए गए उन तरीकों में से किसी एक का इस्तेमाल करें जिनमें Tink ऐसे सिस्टम के साथ काम करता है.
  • उपयोगकर्ता के पास कुंजियों के कुछ हिस्सों का ऐक्सेस होना – ऐसा करने से, अक्सर काम न करने से जुड़ी गड़बड़ियां होती हैं.

असल में, कुछ मामलों में इन सिद्धांतों का उल्लंघन करना ज़रूरी होता है. Tink, ऐसा करने के लिए सुरक्षित तरीके उपलब्ध कराता है. इन तरीकों के बारे में नीचे दिए गए सेक्शन में बताया गया है.

सीक्रेट कुंजी ऐक्सेस टोकन

सीक्रेट पासकोड को ऐक्सेस करने के लिए, उपयोगकर्ताओं के पास टोकन होना चाहिए. यह आम तौर पर किसी क्लास का ऑब्जेक्ट होता है, जिसमें कोई फ़ंक्शन नहीं होता. आम तौर पर, 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 पासकोड में, न सिर्फ़ पासकोड का रॉ वर्शन होता है, बल्कि मेटाडेटा भी होता है. इससे यह पता चलता है कि पासकोड का इस्तेमाल कैसे किया जाना चाहिए. साथ ही, यह भी पता चलता है कि इसका इस्तेमाल किसी और तरीके से नहीं किया जाना चाहिए. उदाहरण के लिए, Tink में मौजूद आरएसए एसएसए पीएसएस कुंजी से पता चलता है कि इस आरएसए कुंजी का इस्तेमाल, सिर्फ़ तय किए गए हैश फ़ंक्शन और तय की गई साल्ट की लंबाई का इस्तेमाल करके, पीएसएस साइनिंग एल्गोरिदम के साथ किया जा सकता है.

कभी-कभी, Tink पासकोड को ऐसे अलग-अलग फ़ॉर्मैट में बदलना ज़रूरी होता है जिनमें साफ़ तौर पर यह मेटाडेटा शामिल न हो. आम तौर पर, इसका मतलब है कि पासकोड का इस्तेमाल करते समय मेटाडेटा देना ज़रूरी है. दूसरे शब्दों में, यह मानते हुए कि कुंजी का इस्तेमाल हमेशा एक ही एल्गोरिदम के साथ किया जाता है, ऐसी कुंजी में अब भी वही मेटाडेटा होता है. इसे सिर्फ़ किसी दूसरी जगह पर सेव किया जाता है.

किसी Tink पासकोड को किसी दूसरे फ़ॉर्मैट में बदलते समय, आपको यह पक्का करना होगा कि Tink पासकोड का मेटाडेटा, दूसरे पासकोड फ़ॉर्मैट के मेटाडेटा से मेल खाता हो. अगर यह मैच नहीं होता है, तो कन्वर्ज़न पूरा नहीं होगा.

अक्सर, ये जांच नहीं की जाती हैं या अधूरी की जाती हैं. इसलिए, Tink उन एपीआई के ऐक्सेस पर पाबंदी लगाता है जो सिर्फ़ कुछ हिस्से के पासकोड का ऐक्सेस देते हैं, लेकिन उसे पूरी पासकोड के तौर पर इस्तेमाल किया जा सकता है. Java में, Tink इसके लिए RestrictedApi का इस्तेमाल करता है. C++ और Golang में, यह पासकोड ऐक्सेस टोकन जैसे टोकन का इस्तेमाल करता है.

इन एपीआई के उपयोगकर्ता, पासकोड का फिर से इस्तेमाल करने से जुड़े हमलों और इनके काम न करने से जुड़ी समस्याओं को रोकने के लिए ज़िम्मेदार होते हैं.

आम तौर पर, आपको ऐसे तरीके मिलते हैं जो Tink से कुंजियों को एक्सपोर्ट करने या उनमें कुंजियां इंपोर्ट करने के संदर्भ में, "कुंजी के कुछ हिस्से के ऐक्सेस" पर पाबंदी लगाते हैं. यहां दिए गए सबसे सही तरीकों से, इन स्थितियों में सुरक्षित तरीके से काम करने का तरीका बताया गया है.

सबसे सही तरीका: एक्सपोर्ट की गई कुंजी के सभी पैरामीटर की पुष्टि करना

उदाहरण के लिए, अगर आपने कोई ऐसा फ़ंक्शन लिखा है जो HPKE सार्वजनिक कुंजी एक्सपोर्ट करता है, तो:

सार्वजनिक पासकोड एक्सपोर्ट करने का गलत तरीका:

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

यह समस्या पैदा करता है. पासकोड मिलने के बाद, उसका इस्तेमाल करने वाला तीसरा पक्ष, पासकोड के पैरामीटर के बारे में कुछ अनुमान लगाता है: उदाहरण के लिए, वह यह अनुमान लगाएगा कि इस 256-बिट पासकोड के लिए इस्तेमाल किया गया एचपीकेई एईएडी एल्गोरिदम, एईएस-जीसीएम था.

सुझाव: पुष्टि करें कि पैरामीटर वही हैं जो आपको मुख्य एक्सपोर्ट में चाहिए.

सार्वजनिक पासकोड एक्सपोर्ट करने का बेहतर तरीका:

/** 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 ऑब्जेक्ट में बदल देते हैं. साथ ही, हम यह भी तय कर देते हैं कि किस एल्गोरिदम का इस्तेमाल किया जाना चाहिए. इस तरीके से, पासवर्ड को गच्चा देने वाले हमलों की संभावना कम हो जाती है.