কোয়ান্টাম-প্রতিরোধী (ML-DSA) বা ক্লাসিক্যাল ('Ed25519', 'ECDSA') অ্যাসিমেট্রিক কী ব্যবহার করে ব্রাউজারে মেসেজ, টোকেন বা আর্টিফ্যাক্ট সাইন ও যাচাই করতে Web Cryptography API (crypto.subtle) ব্যবহার করুন। শেয়ার করা সিক্রেট কী ছাড়াই ডিজিটাল স্বাক্ষর ডেটার ইন্টিগ্রিটি এবং স্বাক্ষরকারীর সত্যতা নিশ্চিত করে। শেয়ার্ড সিক্রেট প্রতিষ্ঠা করা এবং গোপনীয় পে-লোড এনক্রিপ্ট করার জন্য, WebCrypto-এর মাধ্যমে ক্লায়েন্ট-সাইডে ডেটা এনক্রিপ্ট করা দেখুন।
১. অ্যালগরিদম বেছে নিন এবং কী পেয়ার জেনারেট করুন
ML-DSA (মডিউল-ল্যাটিস-ভিত্তিক ডিজিটাল সিগনেচার অ্যালগরিদম, NIST FIPS 204-এ স্ট্যান্ডার্ডাইজ করা) পোস্ট-কোয়ান্টাম ল্যাটিস-ভিত্তিক ডিজিটাল সিগনেচার প্রদান করে যা ক্লাসিক্যাল এবং কোয়ান্টাম কম্পিউটার, উভয়ের মাধ্যমেই করা অ্যাটাক প্রতিরোধ করে।
সাধারণ-উদ্দেশ্যে সাইন করার জন্য ডিফল্ট হিসেবে 'ML-DSA-65' ব্যবহার করুন। ব্যান্ডউইথ-সীমাবদ্ধ চ্যানেলে ছোট সর্বজনীন কী এবং স্বাক্ষর প্রয়োজন হলে এবং ১২৮-বিট নিরাপত্তা যথেষ্ট হলে 'ML-DSA-44' বেছে নিন অথবা নিরাপত্তা নীতির জন্য NIST বিভাগ ৫ (CNSA 2.0) মেনে চলার প্রয়োজন হলে 'ML-DSA-87' বেছে নিন।
| অ্যালগরিদমের নাম | নিরাপত্তা লেভেল ও ব্যবহারের ক্ষেত্র | সর্বজনীন কী ('raw-public') |
স্বাক্ষরের সাইজ |
|---|---|---|---|
'ML-DSA-44' |
NIST ক্যাটাগরি ২ (~১২৮-বিট নিরাপত্তা); সবচেয়ে ছোট কী ও স্বাক্ষর | ১৩১২ বাইট | ২৪২০ বাইট |
'ML-DSA-65' |
NIST বিভাগ ৩ (~১৯২-বিট নিরাপত্তা); সাজেস্ট করা ডিফল্ট | ১৯৫২ বাইট | 3309 bytes |
'ML-DSA-87' |
NIST ক্যাটেগরি ৫ (~২৫৬-বিট নিরাপত্তা); সর্বোচ্চ নিরাপত্তা মার্জিন | ২৫৯২ বাইট | ৪৬২৭ বাইট |
- অ্যালগরিদমের নাম হিসেবে
'ML-DSA-44','ML-DSA-65'বা'ML-DSA-87'পাস করুন (প্রি-স্ট্যান্ডার্ডাইজেশন নাম যেমন'Dilithium'বা'CRYSTALS-Dilithium'NotSupportedErrorথ্রো করে)। - ML-DSA বা
'Ed25519', কোনওটিইhashপ্যারামিটার নেয় না। generateKey()-এর সাথে কী পেয়ার তৈরি করার সময়['sign', 'verify']পাস করুন:
const signerKeyPair = await crypto.subtle.generateKey(
'ML-DSA-65',
false,
['sign', 'verify'],
);
২. 'কী' এক্সপোর্ট ও ইমপোর্ট করা
'ML-DSA-44', 'ML-DSA-65' এবং 'ML-DSA-87' পুরনো 'raw' ফর্ম্যাট বাতিল করুন importKey() এবং exportKey()-এ NotSupportedError সহ। সবসময় নির্দিষ্ট ফর্ম্যাট ব্যবহার করে 'কী' এক্সপোর্ট বা ইমপোর্ট করুন:
'raw-public': পাবলিক কী বাইট (1312,1952বা2592বাইট)। পাবলিক কী ইমপোর্ট করার সময় কী ব্যবহারের জন্য['verify']পাস করুন।'raw-seed': তিনটি ML-DSA অ্যালগরিদমের জন্য ৩২-বাইট ব্যক্তিগত কী সিড।'raw'এবং'raw-private'উভয়ই ML-DSA-তেNotSupportedErrorথ্রো করে—'raw-seed','pkcs8'(৫৪ বাইট) বা'jwk'(kty: 'AKP') ব্যবহার করে ব্যক্তিগত কী স্টোর বা ট্রান্সমিট করে। ব্যক্তিগত কী ইমপোর্ট করার সময় কী ব্যবহারের জন্য['sign']পাস করুন (importKey()-এ['sign', 'verify']পাস করলেSyntaxErrorথ্রো করে)।'spki','pkcs8'এবং'jwk': তিনটি ML-DSA অ্যালগরিদমের জন্যই কাজ করে।
'raw-public'-এর সাথে যাচাইকরণের সর্বজনীন কী সবসময় এক্সপোর্ট করুন যাতে সর্বজনীন কী বাইট সিরিয়ালাইজ করা বা যাচাইকারীদের সাথে শেয়ার করা যায়:
// Export the public key bytes on the signer
const publicKeyBytes = await crypto.subtle.exportKey(
'raw-public',
signerKeyPair.publicKey,
);
// Import the public key bytes on the verifier (usage must be ['verify'] only)
const verifierPublicKey = await crypto.subtle.importKey(
'raw-public',
publicKeyBytes,
'ML-DSA-65',
true,
['verify'],
);
আপনার কাছে শুধু ব্যক্তিগত সাইনিং CryptoKey (এমনকি extractable: false সহ) থাকলে এবং এর সাথে সংশ্লিষ্ট পাবলিক CryptoKey প্রয়োজন হলে, ['verify'] সহ crypto.subtle.getPublicKey() কল করুন:
const publicKey = await crypto.subtle.getPublicKey(
signerKeyPair.privateKey,
['verify'],
);
৩. পে-লোড সাইন ও যাচাই করুন
সিগনেচার তৈরি করতে ব্যক্তিগত কী দিয়ে crypto.subtle.sign() কল করুন এবং এটি চেক করতে সর্বজনীন কী দিয়ে crypto.subtle.verify() কল করুন:
const payloadBytes = new TextEncoder().encode(
'{"artifact":"release-v2.4.0.tar.gz","sha256":"9f86d08..."}',
);
const signatureBuffer = await crypto.subtle.sign(
'ML-DSA-65',
signerKeyPair.privateKey,
payloadBytes,
);
const isValid = await crypto.subtle.verify(
'ML-DSA-65',
verifierPublicKey,
signatureBuffer,
payloadBytes,
);
ContextParams-এর সাথে ডোমেন আলাদা করা
একই কী পেয়ার শেয়ার করে এমন আপনার অ্যাপ্লিকেশন বা প্রোটোকলের অন্য অংশে যাতে একটি উদ্দেশ্যে তৈরি করা স্বাক্ষর আবার ব্যবহার করা না হয়, তার জন্য স্বাক্ষরটিকে একটি ডোমেন-সেপারেশন context-এর সাথে বাইন্ড করুন।
শুধু অ্যালগরিদমের নাম স্ট্রিংয়ের পরিবর্তে, sign() এবং verify()-এ ContextParams ডিকশনারি ({ name, context }) পাস করুন, যেখানে context হল BufferSource (Uint8Array বা ArrayBuffer):
const contextBytes = new TextEncoder().encode('release-manifest-v1');
const signatureBuffer = await crypto.subtle.sign(
{
name: 'ML-DSA-65',
context: contextBytes,
},
signerKeyPair.privateKey,
payloadBytes,
);
const isValid = await crypto.subtle.verify(
{
name: 'ML-DSA-65',
context: contextBytes,
},
verifierPublicKey,
signatureBuffer,
payloadBytes,
);
- ২৫৫-বাইট সীমা:
context0থেকে255বাইট গ্রহণ করে (contextবাদ দেওয়া মানে খালিnew Uint8Array(0)পাস করা)।255বাইটের বেশি পাস করলেOperationErrorসহ বাতিল করা হয়। - সম্পূর্ণ মিল প্রয়োজন:
crypto.subtle.verify()শুধুমাত্র তখনইtrueরিটার্ন করে যখন যাচাইকারীcrypto.subtle.sign()-এর সময় ব্যবহৃতcontextবাইটের সাথে সম্পূর্ণ মিলে যায়।
ফলব্যাক স্ট্র্যাটেজি
আপনার বেসলাইন টার্গেট এখনও SubtleCrypto.supports() বা ML-DSA সাপোর্ট না করলে, স্ট্যাটিক SubtleCrypto.supports() পদ্ধতি ব্যবহার করে সাপোর্ট শনাক্ত করুন এবং ক্লাসিক্যাল 'Ed25519' (অথবা 'Ed25519' সাপোর্ট করার আগে ব্রাউজার টার্গেট করার সময় 'P-256' ও 'SHA-256' সহ 'ECDSA') পদ্ধতিতে ফিরে যান:
SubtleCryptoকনস্ট্রাক্টরেSubtleCrypto.supports()কল করুন,crypto.subtle-এ নয়:globalThis.SubtleCrypto?.supports?.('sign', 'ML-DSA-65')হল একটি সিঙ্ক্রোনাস স্ট্যাটিক পদ্ধতি যা একটি বুলিয়ান রিটার্ন করে (crypto.subtle.supportsহলundefined)।'sign','verify','generateKey','importKey','exportKey'বা'getPublicKey'প্রোব করুন (অথবাSubtleCrypto.supports('sign', { name: 'ML-DSA-65', context })-এর মতো একটিContextParamsডিকশনারি পাস করুন)।- ফলব্যাকে কী ফর্ম্যাট (
'raw'): যেসব ব্রাউজার আধুনিক WebCrypto সাপোর্ট করে সেগুলি'Ed25519'ও'ECDSA'-এ'raw-public'-ও গ্রহণ করে, পুরনো ব্রাউজারকে টার্গেট করে ফলব্যাক পাথে'raw'ব্যবহার করুন। - ফলব্যাকে ডোমেন আলাদা করা: ক্লাসিক্যাল WebCrypto অ্যালগরিদম (
'Ed25519'এবং'ECDSA') অ্যালগরিদম ডিকশনারি থেকেcontextপ্রপার্টি পড়ে না (WebIDL,AlgorithmএবংEcdsaParams-এ অজানা ডিকশনারি প্রপার্টিকে নীরবে উপেক্ষা করে)। আপনার ফলব্যাক পাথে পেলোডে দৈর্ঘ্য-প্রিফিক্সডcontextহেডার প্রিপেন্ড করুন যাতে ডোমেন আলাদা করা দুটি স্যুট জুড়ে ধারাবাহিকভাবে আচরণ করে।
const ML_DSA_PUBLIC_KEY_ALGORITHMS = {
1312: 'ML-DSA-44',
1952: 'ML-DSA-65',
2592: 'ML-DSA-87',
};
export function supportsPostQuantumSignatures() {
return (
typeof globalThis.SubtleCrypto?.supports === 'function' &&
SubtleCrypto.supports('sign', 'ML-DSA-65')
);
}
export async function generateSigningKeyPair() {
if (supportsPostQuantumSignatures()) {
const keyPair = await crypto.subtle.generateKey('ML-DSA-65', false, ['sign', 'verify']);
const publicKeyBytes = new Uint8Array(
await crypto.subtle.exportKey('raw-public', keyPair.publicKey),
);
return {
suite: 'ML-DSA-65',
keyFormat: 'raw-public',
keyPair,
publicKeyBytes,
};
}
// Classical fallback: Ed25519
const keyPair = await crypto.subtle.generateKey('Ed25519', false, ['sign', 'verify']);
const publicKeyBytes = new Uint8Array(await crypto.subtle.exportKey('raw', keyPair.publicKey));
return {
suite: 'Ed25519',
keyFormat: 'raw',
keyPair,
publicKeyBytes,
};
}
function frameFallbackInput(payloadBytes, contextBytes) {
if (contextBytes.byteLength > 255) {
throw new DOMException('Context must not exceed 255 bytes', 'OperationError');
}
const framed = new Uint8Array(1 + contextBytes.byteLength + payloadBytes.byteLength);
framed[0] = contextBytes.byteLength;
framed.set(contextBytes, 1);
framed.set(payloadBytes, 1 + contextBytes.byteLength);
return framed;
}
export async function signPayload(
privateKey,
payloadBytes,
contextBytes = new Uint8Array(0),
) {
if (privateKey.algorithm.name.startsWith('ML-DSA')) {
const sig = await crypto.subtle.sign(
{ name: privateKey.algorithm.name, context: contextBytes },
privateKey,
payloadBytes,
);
return new Uint8Array(sig);
}
const framed = frameFallbackInput(payloadBytes, contextBytes);
const sig = await crypto.subtle.sign('Ed25519', privateKey, framed);
return new Uint8Array(sig);
}
export async function verifyPayload(
publicKeyOrBytes,
signatureBytes,
payloadBytes,
contextBytes = new Uint8Array(0),
) {
const publicKey =
publicKeyOrBytes instanceof CryptoKey
? publicKeyOrBytes
: await (() => {
const bytes =
publicKeyOrBytes instanceof Uint8Array
? publicKeyOrBytes
: new Uint8Array(publicKeyOrBytes);
const mlDsaName = ML_DSA_PUBLIC_KEY_ALGORITHMS[bytes.byteLength];
return mlDsaName
? crypto.subtle.importKey('raw-public', bytes, mlDsaName, true, ['verify'])
: crypto.subtle.importKey('raw', bytes, 'Ed25519', true, ['verify']);
})();
if (publicKey.algorithm.name.startsWith('ML-DSA')) {
return crypto.subtle.verify(
{ name: publicKey.algorithm.name, context: contextBytes },
publicKey,
signatureBytes,
payloadBytes,
);
}
const framed = frameFallbackInput(payloadBytes, contextBytes);
return crypto.subtle.verify('Ed25519', publicKey, signatureBytes, framed);
}