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