কোয়ান্টাম-প্রতিরোধী কী এনক্যাপসুলেশন মেকানিজম (KEMs) বা ক্লাসিক্যাল কী এগ্রিমেন্ট ব্যবহার করে অনির্ভরযোগ্য নেটওয়ার্কে শেয়ার করা গোপনীয়তা প্রতিষ্ঠা করতে এবং অ্যাসোসিয়েটেড ডেটা (AEAD) সহ যাচাইকৃত এনক্রিপশন সহ ব্রাউজারে সংবেদনশীল ডেটা এনক্রিপ্ট করতে ওয়েব ক্রিপ্টোগ্রাফি API (crypto.subtle) ব্যবহার করুন। মেসেজ বা আর্টিফ্যাক্ট এনক্রিপ্ট না করে সাইন ও যাচাই করার জন্য, WebCrypto-এর মাধ্যমে ডেটা সাইন ও যাচাই করুন দেখুন।
এন্ড-টু-এন্ড এনভেলপ এনক্রিপশন ফ্লো দুটি পর্যায়ে বিভক্ত:
- একবার প্রাপক সেট-আপ করা (ধাপ ১–২): প্রাপক একটি কী পেয়ার তৈরি করে, ভবিষ্যতের সেশনের জন্য IndexedDB-তে
CryptoKeyPairসেভ করে এবং প্রেরকদের সাথে শেয়ার করার জন্য সর্বজনীন কী ('raw-public') এক্সপোর্ট করে। - প্রতি মেসেজ এনক্রিপশন (ধাপ ৩–৪): প্রতিটি পে-লোডের জন্য, প্রেরক প্রাপকের সর্বজনীন কী ইমপোর্ট করে, নতুন ওয়ান-টাইম শেয়ার করা কী এবং সাইফারটেক্সট ক্যাপসুল তৈরি করতে
encapsulateKey()কল করে, নতুন ১২-বাইট IV সহ পে-লোড এনক্রিপ্ট করে এবং প্রাপককে{ capsule, iv, ciphertext }পাঠায়।
১. KEM অ্যালগরিদম বেছে নিন এবং প্রাপকের কী পেয়ার জেনারেট করুন
ডিফি-হেলম্যান কী এগ্রিমেন্টের (deriveKey()) মতো নয়, যেখানে উভয় পক্ষ তাদের কী একত্রিত করে, একটি কী এনক্যাপসুলেশন মেকানিজম (KEM) প্রাপকের সর্বজনীন কী ব্যবহার করে একটি একক encapsulateKey() কলে একটি র্যান্ডম শেয়ার্ড সিক্রেট এবং একটি সাইফারটেক্সট ক্যাপসুল উভয়ই তৈরি করে। প্রাপক decapsulateKey() ব্যবহার করে সাইফারটেক্সট ক্যাপসুল থেকে একই শেয়ার করা কী ফিরিয়ে আনেন।
ডিফল্ট হিসেবে 'MLKEM768-X25519' ব্যবহার করুন: এটি পোস্ট-কোয়ান্টাম ML-KEM (NIST FIPS 203-এ স্ট্যান্ডার্ডাইজ করা) এবং ক্লাসিক্যাল 'X25519'-কে একত্রিত করে যাতে যেকোনও একটি অ্যালগরিদম কাজ করা পর্যন্ত শেয়ার্ড সিক্রেট সুরক্ষিত থাকে। যখন কোনও নীতির জন্য হাইব্রিড কম্পোনেন্ট ছাড়া পিওর FIPS 203 আউটপুটের প্রয়োজন হয় তখন স্ট্যান্ডঅ্যালোন 'ML-KEM-768' বেছে নিন, অথবা যখন কোনও নীতির জন্য NIST ক্যাটাগরি 5 (CNSA 2.0) মেনে চলার প্রয়োজন হয় তখন 'ML-KEM-1024' বেছে নিন।
| অ্যালগরিদমের নাম | নিরাপত্তা লেভেল ও ব্যবহারের ক্ষেত্র | সর্বজনীন কী ('raw-public') |
ব্যক্তিগত বীজ ('raw-seed') |
সাইফারটেক্সটের সাইজ |
|---|---|---|---|---|
'MLKEM768-X25519' |
হাইব্রিড পোস্ট-কোয়ান্টাম ('ML-KEM-768') + ক্লাসিক্যাল ('X25519'); সাজেস্ট করা ডিফল্ট |
১২১৬ বাইট | ৩২ বাইট | ১১২০ বাইট |
'ML-KEM-768' |
NIST বিভাগ ৩ (~১৯২-বিট নিরাপত্তা); স্বতন্ত্র পোস্ট-কোয়ান্টাম KEM | ১১৮৪ বাইট | ৬৪ বাইট | ১০৮৮ বাইট |
'ML-KEM-1024' |
NIST ক্যাটেগরি ৫ (~২৫৬-বিট নিরাপত্তা); সর্বোচ্চ নিরাপত্তা মার্জিন | ১৫৬৮ বাইট | ৬৪ বাইট | ১৫৬৮ বাইট |
- অ্যালগরিদমের নাম হিসেবে
'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'],
);
২. 'কী' এক্সপোর্ট ও ইমপোর্ট করা
আধুনিক WebCrypto অ্যালগরিদম ('MLKEM768-X25519', 'ML-KEM-768', 'ML-KEM-1024' এবং 'ChaCha20-Poly1305') পুরনো 'raw' ফর্ম্যাট বাতিল করে importKey() এবং exportKey()-এ NotSupportedError সহ। সবসময় এক্সপ্লিসিট কী ফর্ম্যাট উল্লেখ করুন:
'raw-public': পাবলিক কী বাইট (1216,1184বা1568বাইট)। KEM সর্বজনীন কী ইমপোর্ট করার সময়['encapsulateKey'](বা['encapsulateBits']) পাস করুন।'raw-seed': ব্যক্তিগত কী সিড বাইট ('MLKEM768-X25519'-এর জন্য32বাইট;'ML-KEM-768'ও'ML-KEM-1024'-এর জন্য64বাইট)। KEM ব্যক্তিগত কী ইমপোর্ট করার সময়['decapsulateKey'](বা['decapsulateBits']) পাস করুন (['encapsulateKey', 'decapsulateKey']থেকেimportKey()বাgetPublicKey()পাস করলেSyntaxErrorথ্রো করে)।'raw'ও'raw-private'উভয়ই KEM ব্যক্তিগত কী-তে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'],
);
৩. শেয়ার করা 'কী' এনক্যাপসুলেট ও ডিক্যাপসুলেট করা
encapsulateKey() এবং decapsulateKey(), sharedKeyAlgorithm-এ ('raw-secret' ফর্ম্যাট সহ) নির্দিষ্ট করা অ্যালগরিদম ব্যবহার করে ৩২-বাইট (২৫৬-বিট) KEM শেয়ার্ড সিক্রেট CryptoKey-এ ইমপোর্ট করে। AEAD এনক্রিপশন কী তৈরি করতে সরাসরি '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) সাথে বাইন্ড করতে অথবা একটি এনক্যাপসুলেশন থেকে একাধিক সাব-কী পেতে, ['deriveKey'] ব্যবহারের সাথে sharedKeyAlgorithm হিসেবে 'HKDF' পাস করুন, তারপর 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'],
);
৪. পেলোড এনক্রিপ্ট ও ডিক্রিপ্ট করা ('ChaCha20-Poly1305' এবং 'AES-GCM')
প্রতিটি এনক্রিপশন অপারেশনের জন্য নতুন, ক্রিপ্টোগ্রাফিক্যালি র্যান্ডম ১২-বাইট (৯৬-বিট) ইনিশিয়ালাইজেশন ভেক্টর (iv) ব্যবহার করে AEAD অ্যালগরিদম ('ChaCha20-Poly1305' বা 'AES-GCM') দিয়ে পেলোড ডেটা এনক্রিপ্ট করুন। (iv, key) পেয়ার কখনও আবার ব্যবহার করবেন না। ডেডিকেটেড AES হার্ডওয়্যার অ্যাক্সিলারেশন ছাড়াই ডিভাইস জুড়ে কনসিস্টেন্ট কনস্ট্যান্ট-টাইম পারফর্ম্যান্সের জন্য 'ChaCha20-Poly1305' বেছে নিন অথবা আগে থেকে থাকা WebCrypto বা FIPS 140 ডেপ্লয়মেন্টের সাথে ইন্টারঅপারেট করার সময় 'AES-GCM' বেছে নিন।
'ChaCha20-Poly1305': একটি নির্দিষ্ট ২৫৬-বিট (৩২-বাইট) কী ব্যবহার করে (generateKey('ChaCha20-Poly1305', extractable, ['encrypt', 'decrypt'])একটি স্ট্রিং বা{ name: 'ChaCha20-Poly1305' }নেয়, যেখানে কোনওlengthপ্রপার্টি নেই)।ivঠিক ১২ বাইটের হতে হবে এবং ১২৮-বিট Poly1305 যাচাইকরণ ট্যাগ সাইফারটেক্সটে যোগ করা হয়।'AES-GCM':generateKey()বাderiveKey()কল করার সময়{ name: 'AES-GCM', length: 256 }পাস করুন এবংencrypt()ওdecrypt()কল করার সময় ১২-বাইট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', অথবা 'X25519' সাপোর্ট করার আগে ব্রাউজার টার্গেট করার সময় 'P-256' সহ 'ECDH') ফিরে যান:
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)-এর মাধ্যমে ২৫৬ বিট বের করুন এবং ২৫৬-বিট'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);
}