Web Cryptography API (crypto.subtle) का इस्तेमाल करके, असुरक्षित नेटवर्क पर शेयर किए गए सीक्रेट को सुरक्षित बनाएं. इसके लिए, क्वांटम-रेज़िस्टेंट की एन्कैप्सुलेशन मैकेनिज़्म (केईएम) या क्लासिकल की एग्रीमेंट का इस्तेमाल करें. साथ ही, ब्राउज़र में संवेदनशील डेटा को एन्क्रिप्ट करने के लिए, Authenticated Encryption with Associated Data (AEAD) का इस्तेमाल करें. मैसेज या आर्टफ़ैक्ट को एन्क्रिप्ट (सुरक्षित) किए बिना उन पर हस्ताक्षर करने और उनकी पुष्टि करने के लिए, WebCrypto की मदद से डेटा पर हस्ताक्षर करना और उसकी पुष्टि करना लेख पढ़ें.
एंड-टू-एंड एन्वेलप एन्क्रिप्शन का फ़्लो दो चरणों में बंटा होता है:
- एक बार में पाने वाले व्यक्ति का सेटअप (पहला और दूसरा चरण): पाने वाला व्यक्ति, कुंजी का जोड़ा जनरेट करता है. साथ ही, आने वाले सेशन के लिए
CryptoKeyPairको IndexedDB में सेव करता है. इसके बाद, भेजने वाले लोगों के साथ शेयर करने के लिए, सार्वजनिक पासकोड ('raw-public') को एक्सपोर्ट करता है. - हर मैसेज को एन्क्रिप्ट (सुरक्षित) करना (तीसरा और चौथा चरण): हर पेलोड के लिए, भेजने वाला व्यक्ति, पाने वाले व्यक्ति का सार्वजनिक पासकोड इंपोर्ट करता है. इसके बाद, वह
encapsulateKey()को कॉल करके, एक बार इस्तेमाल होने वाला नया शेयर किया गया पासकोड और साइफ़रटेक्स्ट कैप्सूल जनरेट करता है. साथ ही, 12-बाइट के नए आईवी के साथ पेलोड को एन्क्रिप्ट (सुरक्षित) करता है और पाने वाले व्यक्ति को{ capsule, iv, ciphertext }भेजता है.
1. कोई केईएम एल्गोरिदम चुनें और पाने वाले के लिए कुंजी का जोड़ा जनरेट करें
डिफ़ी-हेलमैन की-एग्रीमेंट (deriveKey()) में, दोनों पक्ष अपने पासकोड को जोड़ते हैं. हालांकि, की एन्कैप्सुलेशन मैकेनिज़्म (केईएम) में, पाने वाले के सार्वजनिक पासकोड का इस्तेमाल करके, एक ही encapsulateKey() कॉल में रैंडम शेयर किया गया सीक्रेट और साइफ़रटेक्स्ट कैप्सूल, दोनों जनरेट किए जाते हैं. मैसेज पाने वाला व्यक्ति, decapsulateKey() का इस्तेमाल करके, सादे टेक्स्ट को एन्क्रिप्ट करने वाले कैप्सूल से, शेयर की गई कुंजी को वापस पाता है.
डिफ़ॉल्ट रूप से 'MLKEM768-X25519' का इस्तेमाल करें: इसमें पोस्ट-क्वांटम एमएल-केईएम (NIST FIPS 203 में स्टैंडर्ड के तौर पर शामिल) और क्लासिकल 'X25519' को जोड़ा जाता है. इससे शेयर किया गया सीक्रेट तब तक सुरक्षित रहता है, जब तक दोनों में से कोई एक एल्गोरिदम काम करता है. अगर किसी नीति के तहत, हाइब्रिड कॉम्पोनेंट के बिना सिर्फ़ FIPS 203 आउटपुट की ज़रूरत है, तो स्टैंडअलोन 'ML-KEM-768' चुनें. अगर किसी नीति के तहत, NIST कैटगरी 5 (सीएनएसए 2.0) का पालन करना ज़रूरी है, तो 'ML-KEM-1024' चुनें.
| एल्गोरिदम का नाम | सुरक्षा का स्तर और इस्तेमाल का उदाहरण | सार्वजनिक पासकोड ('raw-public') |
प्राइवेट सीड ('raw-seed') |
सादे टेक्स्ट को एन्क्रिप्ट करने का साइज़ |
|---|---|---|---|---|
'MLKEM768-X25519' |
हाइब्रिड पोस्ट-क्वांटम ('ML-KEM-768') + क्लासिकल ('X25519'); डिफ़ॉल्ट के तौर पर सुझाया गया |
1216 बाइट | 32 बाइट | 1120 बाइट |
'ML-KEM-768' |
एनआईएसटी कैटगरी 3 (~192-बिट सुरक्षा); स्टैंडअलोन पोस्ट-क्वांटम केईएम | 1184 बाइट | 64 बाइट | 1088 बाइट |
'ML-KEM-1024' |
एनआईएसटी कैटगरी 5 (~256-बिट सुरक्षा); सबसे ज़्यादा सुरक्षा मार्जिन | 1568 बाइट | 64 बाइट | 1568 बाइट |
- एल्गोरिदम के नाम के तौर पर
'MLKEM768-X25519','ML-KEM-768'या'ML-KEM-1024'पास करें.'X-Wing'या'X25519MLKEM768'जैसे उपनामों के साथ-साथ'ML-KEM-512',NotSupportedErrorको थ्रो करते हैं. - KEM कुंजी का जोड़ा जनरेट करते समय,
extractable: falseऔर['encapsulateKey', 'decapsulateKey'](या['encapsulateBits', 'decapsulateBits']) पास करें. निकाले न जा सकने वालेCryptoKeyऔरCryptoKeyPairऑब्जेक्ट को स्ट्रक्चर्ड क्लोनिंग (objectStore.put(recipientKeyPair, 'recipient-kem-key')) की मदद से, सीधे IndexedDB में सभी सेशन के लिए सेव रखा जा सकता है. इसके लिए, निजी पासकोड के बाइट को एक्सपोर्ट करने की ज़रूरत नहीं होती:
const recipientKeyPair = await crypto.subtle.generateKey(
'MLKEM768-X25519',
false,
['encapsulateKey', 'decapsulateKey'],
);
2. पासकोड एक्सपोर्ट और इंपोर्ट करना
मॉडर्न WebCrypto एल्गोरिदम ('MLKEM768-X25519', 'ML-KEM-768', 'ML-KEM-1024', और 'ChaCha20-Poly1305'), importKey() और exportKey() में NotSupportedError के साथ पुराने 'raw' फ़ॉर्मैट को अस्वीकार कर देते हैं. हमेशा साफ़ तौर पर कुंजी का फ़ॉर्मैट बताएं:
'raw-public': सार्वजनिक पासकोड के बाइट (1216,1184या1568बाइट). केईएम सार्वजनिक पासकोड इंपोर्ट करते समय,['encapsulateKey'](या['encapsulateBits']) पास करें.'raw-seed': निजी पासकोड के सीड बाइट ('MLKEM768-X25519'के लिए32बाइट;'ML-KEM-768'और'ML-KEM-1024'के लिए64बाइट). केईएम निजी पासकोड इंपोर्ट करते समय,['decapsulateKey'](या['decapsulateBits']) पास करें (['encapsulateKey', 'decapsulateKey']कोimportKey()याgetPublicKey()पर पास करने सेSyntaxErrorथ्रो होता है).'raw'और'raw-private', दोनों केईएम निजी पासकोड परNotSupportedErrorथ्रो करते हैं. ध्यान दें कि'MLKEM768-X25519'सिर्फ़'raw-public','raw-seed', और'jwk'के साथ काम करता है. यह'spki'या'pkcs8'के साथ काम नहीं करता.'raw-secret': सिमेट्रिक सीक्रेट पासकोड बाइट ('ChaCha20-Poly1305', और'AES-GCM'और'HKDF'पर स्वीकार किया जाता है).
'raw-public' का इस्तेमाल करके, ईमेल पाने वाले व्यक्ति की सार्वजनिक कुंजी को एक्सपोर्ट करें, ताकि सार्वजनिक कुंजी के बाइट को क्रम से लगाया जा सके (उदाहरण के लिए, new Uint8Array(recipientPublicKeyBytes).toBase64() या हेक्स के ज़रिए Base64 के तौर पर) और ईमेल भेजने वाले लोगों के साथ शेयर किया जा सके:
// Export the recipient's public key bytes
const recipientPublicKeyBytes = await crypto.subtle.exportKey(
'raw-public',
recipientKeyPair.publicKey,
);
// Import the recipient's public key on the sender (usage must be ['encapsulateKey'])
const importedPublicKey = await crypto.subtle.importKey(
'raw-public',
recipientPublicKeyBytes,
'MLKEM768-X25519',
true,
['encapsulateKey'],
);
अगर आपके पास सिर्फ़ निजी CryptoKey है (भले ही, आपके पास extractable: false हो) और आपको इससे जुड़ा सार्वजनिक CryptoKey चाहिए, तो crypto.subtle.getPublicKey() को कॉल करें:
const publicKey = await crypto.subtle.getPublicKey(
recipientKeyPair.privateKey,
['encapsulateKey'],
);
3. शेयर की गई कुंजी को एन्कैप्सुलेट और डिकैप्सुलेट करना
encapsulateKey() और decapsulateKey(), sharedKeyAlgorithm में बताए गए एल्गोरिदम ('raw-secret' फ़ॉर्मैट के साथ) का इस्तेमाल करके, 32-बाइट (256-बिट) केईएम शेयर किए गए सीक्रेट को CryptoKey में इंपोर्ट करते हैं. एईएडी एन्क्रिप्शन कुंजी जनरेट करने के लिए, सीधे 'ChaCha20-Poly1305' या 'AES-GCM' पास करें:
// Sender encapsulates a 256-bit AEAD key and produces a ciphertext capsule
const { sharedKey: senderKey, ciphertext: encapsulatedCiphertext } =
await crypto.subtle.encapsulateKey(
'MLKEM768-X25519',
importedPublicKey,
'ChaCha20-Poly1305', // Or 'AES-GCM'
false,
['encrypt', 'decrypt'],
);
// Recipient decapsulates the ciphertext capsule to recover the identical AEAD key
const recipientKey = await crypto.subtle.decapsulateKey(
'MLKEM768-X25519',
recipientKeyPair.privateKey,
encapsulatedCiphertext,
'ChaCha20-Poly1305',
false,
['encrypt', 'decrypt'],
);
'HKDF' के साथ डोमेन सेपरेशन
शेयर की गई कुंजी को किसी खास प्रोटोकॉल कॉन्टेक्स्ट (info और salt) से बाइंड करने या एक ही एन्कैप्सुलेशन से कई सब-की जनरेट करने के लिए, 'HKDF' को sharedKeyAlgorithm के तौर पर पास करें. साथ ही, ['deriveKey'] का इस्तेमाल करें. इसके बाद, crypto.subtle.deriveKey() को कॉल करें:
const { sharedKey: hkdfBaseKey, ciphertext } = await crypto.subtle.encapsulateKey(
'MLKEM768-X25519',
importedPublicKey,
'HKDF',
false,
['deriveKey'],
);
const aeadKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: new Uint8Array(32),
info: new TextEncoder().encode('envelope-v1'),
},
hkdfBaseKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt'],
);
4. पेलोड को एन्क्रिप्ट और डिक्रिप्ट करना ('ChaCha20-Poly1305' और 'AES-GCM')
हर एन्क्रिप्शन ऑपरेशन के लिए, नए और क्रिप्टोग्राफ़िक तरीके से रैंडम 12-बाइट (96-बिट) इनिशलाइज़ेशन वेक्टर (iv) का इस्तेमाल करके, एईएडी एल्गोरिदम ('ChaCha20-Poly1305' या 'AES-GCM') से पेलोड डेटा को एन्क्रिप्ट करें. (iv, key) पेयर का फिर से इस्तेमाल न करें. बिना एईएस हार्डवेयर एक्सेलरेशन वाले डिवाइसों पर, एक जैसी परफ़ॉर्मेंस के लिए 'ChaCha20-Poly1305' चुनें. इसके अलावा, मौजूदा WebCrypto या FIPS 140 डिप्लॉयमेंट के साथ इंटरऑपरेट करते समय 'AES-GCM' चुनें.
'ChaCha20-Poly1305': यह 256-बिट (32-बाइट) की तय कुंजी का इस्तेमाल करता है.generateKey('ChaCha20-Poly1305', extractable, ['encrypt', 'decrypt']), स्ट्रिंग या{ name: 'ChaCha20-Poly1305' }को बिनाlengthप्रॉपर्टी के लेता है.ivठीक 12 बाइट का होना चाहिए. साथ ही, 128-बिट Poly1305 ऑथेंटिकेशन टैग को सादे टेक्स्ट को एन्क्रिप्ट करने के साथ जोड़ा जाता है.'AES-GCM':generateKey()याderiveKey()को कॉल करते समय{ name: 'AES-GCM', length: 256 }पास करें. साथ ही,encrypt()औरdecrypt()को कॉल करते समय 12-बाइट काivपास करें.additionalData(AAD): एन्क्रिप्ट (सुरक्षित) नहीं किए गए मेटाडेटा (जैसे, रिकॉर्ड आईडी, पाने वाले का आईडी या प्रोटोकॉल वर्शन) कोadditionalDataके ज़रिए पुष्टि करने वाले टैग से बाइंड करें. इससे, सादे टेक्स्ट को एन्क्रिप्ट करने या मेटाडेटा में छेड़छाड़ करने पर,decrypt()OperationErrorके साथ अस्वीकार कर देता है.
/**
* Encrypts UTF-8 text with an AEAD CryptoKey ('ChaCha20-Poly1305' or 'AES-GCM').
* @param {CryptoKey} key
* @param {string} plaintext
* @param {Uint8Array} [additionalData]
*/
export async function encryptMessage(key, plaintext, additionalData = new Uint8Array(0)) {
// Always generate a fresh 12-byte (96-bit) IV per message
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(plaintext);
const ciphertextBuffer = await crypto.subtle.encrypt(
{
name: key.algorithm.name,
iv,
additionalData,
},
key,
encoded,
);
return { iv, ciphertext: new Uint8Array(ciphertextBuffer) };
}
/**
* Decrypts and authenticates an AEAD payload. Throws OperationError if tampered.
* @param {CryptoKey} key
* @param {Uint8Array} iv
* @param {Uint8Array} ciphertext
* @param {Uint8Array} [additionalData]
*/
export async function decryptMessage(key, iv, ciphertext, additionalData = new Uint8Array(0)) {
const plaintextBuffer = await crypto.subtle.decrypt(
{
name: key.algorithm.name,
iv,
additionalData,
},
key,
ciphertext,
);
return new TextDecoder().decode(plaintextBuffer);
}
फ़ॉलबैक रणनीतियां
अगर आपका बेसलाइन टारगेट अब तक SubtleCrypto.supports(), encapsulateKey() या 'ChaCha20-Poly1305' के साथ काम नहीं करता है, तो static SubtleCrypto.supports() तरीके का इस्तेमाल करके, यह पता लगाएं कि यह काम करता है या नहीं. इसके बाद, कुछ समय के लिए इस्तेमाल होने वाले स्टैटिक डिफ़ी-हेलमैन की एग्रीमेंट ('X25519' + 'HKDF' + 'AES-GCM' या 'ECDH' के साथ 'P-256', जब 'X25519' के साथ काम करने से पहले ब्राउज़र को टारगेट किया जाता है) पर वापस जाएं:
crypto.subtleके बजायSubtleCryptoकंस्ट्रक्टर परSubtleCrypto.supports()को कॉल करें:globalThis.SubtleCrypto?.supports?.('encapsulateKey', 'MLKEM768-X25519', 'ChaCha20-Poly1305')एक सिंक्रोनस स्टैटिक तरीका है, जो बूलियन (crypto.subtle.supports,undefinedहै) दिखाता है. ध्यान दें किSubtleCrypto.supports(), ऑपरेशन के हिसाब से पैरामीटर की पुष्टि करता है: सिर्फ़ एल्गोरिदम स्ट्रिंग,'encapsulateKey','decapsulateKey','generateKey','importKey','exportKey', और'getPublicKey'के लिए काम करती हैं. हालांकि,'encrypt'या'decrypt'की सीधे तौर पर जांच करने के लिए,iv(उदाहरण के लिए,SubtleCrypto.supports('encrypt', { name: 'ChaCha20-Poly1305', iv: new Uint8Array(12) })) की ज़रूरत होती है.- फ़ॉलबैक (
'raw') में मुख्य फ़ॉर्मैट: मॉडर्न WebCrypto के साथ काम करने वाले ब्राउज़र,'X25519'और'ECDH'पर'raw-public'को भी स्वीकार करते हैं. हालांकि, पुराने ब्राउज़र को टारगेट करने वाले फ़ॉलबैक पाथ में'raw'का इस्तेमाल करें. - क्लासिकल फ़ॉलबैक (
'X25519'+'HKDF'+'AES-GCM'): जबSubtleCrypto.supports()उपलब्ध न हो याfalseदिखाता हो, तो भेजने वाले के लिए कुछ समय के लिए'X25519'कुंजी का जोड़ा जनरेट करें. कुछ समय के लिए उपलब्ध सार्वजनिक कुंजी को कैप्सूल बाइट के तौर पर एक्सपोर्ट करें.deriveBits({ name: 'X25519', public: peerPublicKey }, privateKey, 256)के ज़रिए 256 बिट जनरेट करें. साथ ही, 256-बिट'AES-GCM'कुंजी जनरेट करने के लिए, उन बिट को'HKDF'('SHA-256') के ज़रिए पास करें.
const KEM_PUBLIC_KEY_ALGORITHMS = {
1184: 'ML-KEM-768',
1216: 'MLKEM768-X25519',
1568: 'ML-KEM-1024',
};
export function supportsPostQuantumEnvelope() {
return (
typeof globalThis.SubtleCrypto?.supports === 'function' &&
SubtleCrypto.supports('encapsulateKey', 'MLKEM768-X25519', 'ChaCha20-Poly1305')
);
}
export async function generateRecipientKeys() {
if (supportsPostQuantumEnvelope()) {
const keyPair = await crypto.subtle.generateKey(
'MLKEM768-X25519',
false,
['encapsulateKey', 'decapsulateKey'],
);
const publicKeyBytes = new Uint8Array(
await crypto.subtle.exportKey('raw-public', keyPair.publicKey),
);
return { suite: 'MLKEM768-X25519+ChaCha20-Poly1305', keyPair, publicKeyBytes };
}
// Classical fallback: X25519 key agreement + HKDF + AES-GCM
const keyPair = await crypto.subtle.generateKey('X25519', false, ['deriveBits']);
const publicKeyBytes = new Uint8Array(
await crypto.subtle.exportKey('raw', keyPair.publicKey),
);
return { suite: 'X25519+HKDF+AES-GCM', keyPair, publicKeyBytes };
}
async function deriveFallbackAesKey(privateKey, peerPublicKey, infoBytes = new Uint8Array(0)) {
const sharedSecret = await crypto.subtle.deriveBits(
{ name: 'X25519', public: peerPublicKey },
privateKey,
256,
);
const hkdfKey = await crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false, ['deriveKey']);
return crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: new Uint8Array(32),
info: infoBytes,
},
hkdfKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt'],
);
}
export async function encapsulateEnvelopeKey(
recipientPublicKeyOrBytes,
infoBytes = new Uint8Array(0),
) {
const pubKey =
recipientPublicKeyOrBytes instanceof CryptoKey
? recipientPublicKeyOrBytes
: await (() => {
const bytes =
recipientPublicKeyOrBytes instanceof Uint8Array
? recipientPublicKeyOrBytes
: new Uint8Array(recipientPublicKeyOrBytes);
const kemName = KEM_PUBLIC_KEY_ALGORITHMS[bytes.byteLength];
return kemName
? crypto.subtle.importKey('raw-public', bytes, kemName, true, ['encapsulateKey'])
: crypto.subtle.importKey('raw', bytes, 'X25519', true, []);
})();
if (pubKey.algorithm.name !== 'X25519') {
const { sharedKey, ciphertext } = await crypto.subtle.encapsulateKey(
pubKey.algorithm.name,
pubKey,
'ChaCha20-Poly1305',
false,
['encrypt', 'decrypt'],
);
return { sharedKey, capsuleBytes: new Uint8Array(ciphertext) };
}
// Classical fallback: generate ephemeral X25519 key pair and send ephemeral public key as capsule
const ephemeral = await crypto.subtle.generateKey('X25519', false, ['deriveBits']);
const capsuleBytes = new Uint8Array(await crypto.subtle.exportKey('raw', ephemeral.publicKey));
const sharedKey = await deriveFallbackAesKey(ephemeral.privateKey, pubKey, infoBytes);
return { sharedKey, capsuleBytes };
}
export async function decapsulateEnvelopeKey(
recipientPrivateKey,
capsuleBytes,
infoBytes = new Uint8Array(0),
) {
if (recipientPrivateKey.algorithm.name !== 'X25519') {
return crypto.subtle.decapsulateKey(
recipientPrivateKey.algorithm.name,
recipientPrivateKey,
capsuleBytes,
'ChaCha20-Poly1305',
false,
['encrypt', 'decrypt'],
);
}
const ephemeralPubKey = await crypto.subtle.importKey('raw', capsuleBytes, 'X25519', true, []);
return deriveFallbackAesKey(recipientPrivateKey, ephemeralPubKey, infoBytes);
}