Kuantuma dayanıklı (ML-DSA) veya klasik ('Ed25519', 'ECDSA') asimetrik anahtarlar kullanarak tarayıcıda mesajları, jetonları veya yapıları imzalamak ve doğrulamak için Web Cryptography API'yi (crypto.subtle) kullanın. Dijital imzalar, paylaşılan gizli anahtarlara gerek kalmadan veri bütünlüğünü ve imzalayanın kimliğini garanti eder. Paylaşılan gizli anahtarlar oluşturma ve gizli yükleri şifreleme hakkında bilgi edinmek için WebCrypto ile istemci tarafında verileri şifreleme başlıklı makaleyi inceleyin.
1. Algoritma seçme ve anahtar çifti oluşturma
ML-DSA (NIST FIPS 204'te standartlaştırılmış Modül Kafes Tabanlı Dijital İmza Algoritması), hem klasik hem de kuantum bilgisayarların saldırılarına karşı dayanıklı, kuantum sonrası kafes tabanlı dijital imzalar sağlar.
Genel amaçlı imzalama için varsayılan olarak 'ML-DSA-65' kullanın. Bant genişliği kısıtlaması olan kanallarda daha küçük ortak anahtarlara ve imzalara ihtiyacınız varsa ve 128 bit güvenlik yeterliyse 'ML-DSA-44''i, güvenlik politikası NIST Kategori 5 (CNSA 2.0) uyumluluğu gerektiriyorsa 'ML-DSA-87''i seçin.
| Algoritma adı | Güvenlik seviyesi ve kullanım alanı | Ortak anahtar ('raw-public') |
İmza boyutu |
|---|---|---|---|
'ML-DSA-44' |
NIST Kategori 2 (~128 bit güvenlik); en küçük anahtarlar ve imzalar | 1312 bayt | 2.420 bayt |
'ML-DSA-65' |
NIST Kategori 3 (~192 bit güvenlik); önerilen varsayılan | 1.952 bayt | 3309 bayt |
'ML-DSA-87' |
NIST Kategori 5 (~256 bit güvenlik); en yüksek güvenlik marjı | 2592 bayt | 4.627 bayt |
- Algoritma adı olarak
'ML-DSA-44','ML-DSA-65'veya'ML-DSA-87'değerini iletin ('Dilithium'veya'CRYSTALS-Dilithium'gibi standartlaştırma öncesi adlarNotSupportedErrorhatasına neden olur). - ML-DSA ve
'Ed25519',hashparametresi almaz. generateKey()ile anahtar çifti oluştururken['sign', 'verify']parametresini iletin:
const signerKeyPair = await crypto.subtle.generateKey(
'ML-DSA-65',
false,
['sign', 'verify'],
);
2. Anahtarları dışa ve içe aktarma
'ML-DSA-44', 'ML-DSA-65' ve 'ML-DSA-87', importKey() ve exportKey()'te eski 'raw' biçimini reddeder ve NotSupportedError döndürür. Anahtarları her zaman açık bir biçim kullanarak dışa veya içe aktarın:
'raw-public': Ortak anahtar baytları (1312,1952veya2592bayt). Ortak anahtar içe aktarılırken anahtar kullanımı olarak['verify']değerini iletin.'raw-seed': Üç ML-DSA algoritmasının tümü için 32 baytlık özel anahtar çekirdeği. Hem'raw'hem de'raw-private', ML-DSA'daNotSupportedErrorhatası verir. Özel anahtarları'raw-seed','pkcs8'(54 bayt) veya'jwk'(kty: 'AKP') kullanarak depolayın ya da iletin. Özel anahtar içe aktarırken anahtar kullanımı olarak['sign']değerini iletin (importKey()değerine['sign', 'verify']iletildiğindeSyntaxErrorhatası verilir).'spki','pkcs8've'jwk': Üç ML-DSA algoritması için de desteklenir.
Ortak anahtar baytlarının serileştirilebilmesi veya doğrulayıcılarla paylaşılabilmesi için doğrulama ortak anahtarını her zaman 'raw-public' ile dışa aktarın:
// 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'],
);
Yalnızca özel bir imzalama CryptoKey anahtarınız varsa (extractable: false ile bile) ve buna karşılık gelen herkese açık CryptoKey anahtarına ihtiyacınız varsa ['verify'] ile crypto.subtle.getPublicKey() komutunu çağırın:
const publicKey = await crypto.subtle.getPublicKey(
signerKeyPair.privateKey,
['verify'],
);
3. Yükleri imzalama ve doğrulama
İmza oluşturmak için özel anahtarla crypto.subtle.sign(), imzayı kontrol etmek için ortak anahtarla crypto.subtle.verify() yöntemini çağırın:
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 ile alan adı ayırma
Bir amaç için oluşturulan imzanın, uygulamanızın veya protokolünüzün aynı anahtar çiftini paylaşan başka bir bölümünde yeniden kullanılmasını önlemek için imzayı bir alan adı ayırma context değerine bağlayın.
Yalnızca algoritma adı dizesi yerine sign() ve verify()'ye bir ContextParams sözlüğü ({ name, context }) iletin. Burada context, bir BufferSource (Uint8Array veya ArrayBuffer) olur:
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,
);
- 255 bayt sınırı:
context,0ila255bayt kabul eder (context'nin atlanması, boş birnew Uint8Array(0)iletilmesine eşdeğerdir).255bayttan fazlası iletildiğindeOperationErrorile reddedilir. - Tam eşleşme gerekir:
crypto.subtle.verify(), yalnızca doğrulayıcıcrypto.subtle.sign()sırasında kullanılancontextbaytın aynısını ilettiğindetruedeğerini döndürür.
Yedek stratejiler
Temel hedefiniz henüz SubtleCrypto.supports() veya ML-DSA'yı desteklemiyorsa statik SubtleCrypto.supports() yöntemini kullanarak desteği tespit edin ve klasik 'Ed25519''ya (veya 'Ed25519' desteğinden önceki tarayıcıları hedeflerken 'P-256' ve 'SHA-256' ile 'ECDSA''ya) geri dönün:
SubtleCrypto.supports()yönteminicrypto.subtleüzerinde değil,SubtleCryptooluşturucusunda çağırın:globalThis.SubtleCrypto?.supports?.('sign', 'ML-DSA-65'), boole değeri döndüren eşzamanlı bir statik yöntemdir (crypto.subtle.supports,undefineddeğeridir).'sign','verify','generateKey','importKey','exportKey'veya'getPublicKey'yöntemini inceleyin (ya daSubtleCrypto.supports('sign', { name: 'ML-DSA-65', context })gibi birContextParamssözlüğü iletin).- Yedek anahtar biçimi (
'raw'): Modern WebCrypto'yu destekleyen tarayıcılar'Ed25519've'ECDSA'üzerinde'raw-public''yi de kabul ederken, eski tarayıcıları hedefleyen yedek yollarda'raw''yi kullanın. - Yedek çözümde alan ayrımı: Klasik WebCrypto algoritmaları (
'Ed25519've'ECDSA'), algoritma sözlüğünden bircontextözelliğini okumaz (WebIDL,AlgorithmveEcdsaParamsüzerindeki bilinmeyen sözlük özelliklerini sessizce yoksayar). Alan ayrımının her iki pakette de tutarlı bir şekilde davranması için yedek yolunuzdaki yüke uzunluk ön ekli bircontextbaşlığı ekleyin.
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);
}