Signatur
Beschreibung
crypto.subtle.sign() berechnet eine kryptografische Signatur über die übergebenen Daten. Je nach Algorithmus entsteht dabei eine asymmetrische digitale Signatur (RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, Ed25519) mit einem privaten Schlüssel oder ein symmetrischer Message Authentication Code (HMAC) mit einem geheimen Schlüssel. Die Signatur prüft man anschließend mit crypto.subtle.verify().
Typische Einsatzfälle sind das Signieren von API-Anfragen (z. B. HMAC-Header für Webhooks), das Erzeugen selbst ausgestellter Tokens (JWT mit HS256 oder ES256), der Nachweis der Unversehrtheit von Daten, die über unsichere Kanäle übertragen oder in localStorage/IndexedDB gespeichert werden, sowie Challenge-Response-Verfahren.
Der verwendete CryptoKey muss zum Algorithmus passen und in seinen usages den Wert "sign" enthalten. Die Methode ist asynchron und liefert ein Promise, das mit einem ArrayBuffer erfüllt wird. Für die Übertragung wird dieser meist nach Base64, Base64url oder Hex umgewandelt.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $algorithm Pflicht | string | AlgorithmIdentifier | RsaPssParams | EcdsaParams | Signaturalgorithmus. "RSASSA-PKCS1-v1_5", "HMAC" und "Ed25519" als String oder { name }; RSA-PSS braucht { name: "RSA-PSS", saltLength } (z. B. 32); ECDSA braucht { name: "ECDSA", hash: "SHA-256" }. |
|
| $key Pflicht | CryptoKey | Privater Schlüssel (asymmetrisch) bzw. geheimer Schlüssel (HMAC), dessen algorithm zum Parameter passt und dessen usages "sign" enthält. |
|
| $data Pflicht | ArrayBuffer | TypedArray | DataView | Die zu signierenden Daten als Binärpuffer, z. B. per new TextEncoder().encode(text) erzeugt. |
Rückgabewert
Promise, das mit der Signatur als ArrayBuffer erfüllt wird. Bei ungültigem Schlüssel oder fehlender Nutzung "sign" wird es mit einer InvalidAccessError-DOMException abgelehnt.Beispiele
HMAC-SHA256 für eine Webhook-Signatur
const encoder = new TextEncoder();
const toHex = (buffer) =>
[...new Uint8Array(buffer)].map((b) => b.toString(16).padStart(2, '0')).join('');
const signPayload = async (secret, payload) => {
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(payload));
return toHex(signature);
};
const sig = await signPayload('mein-geheimnis', JSON.stringify({ event: 'order.paid', id: 42 }));
console.log(sig.length, sig);
ECDSA P-256: signieren und verifizieren
const { privateKey, publicKey } = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
const data = new TextEncoder().encode('Vertrag Nr. 2024-17');
const algo = { name: 'ECDSA', hash: 'SHA-256' };
const signature = await crypto.subtle.sign(algo, privateKey, data);
console.log('Länge:', signature.byteLength);
const ok = await crypto.subtle.verify(algo, publicKey, signature, data);
console.log('Gültig:', ok);
RSA-PSS-Signatur als Base64
const { privateKey } = await crypto.subtle.generateKey(
{
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
false,
['sign', 'verify']
);
const signature = await crypto.subtle.sign(
{ name: 'RSA-PSS', saltLength: 32 },
privateKey,
new TextEncoder().encode('Hallo Welt')
);
const base64 = btoa(String.fromCharCode(...new Uint8Array(signature)));
console.log(signature.byteLength, base64.slice(0, 16) + '…');
// Wichtig · Fallstricke
- Nur in sicheren Kontexten:
crypto.subtlegibt es ausschließlich unter HTTPS bzw.localhost. Unter HTTP ist esundefined. - Schlüsselnutzung: Fehlt
\"sign\"in denusagesoder wird ein öffentlicher Schlüssel übergeben, lehnt das Promise mitInvalidAccessErrorab. - ECDSA-Format: Die Web Crypto API liefert ECDSA-Signaturen im rohen
r||s-Format (IEEE P1363, 64 Byte bei P-256), nicht DER-kodiert wie OpenSSL oder Java. Für den Austausch mit solchen Systemen muss man umwandeln. Für JWT (ES256) passt das Rohformat direkt. - Nicht deterministisch: ECDSA und RSA-PSS nutzen Zufallswerte. Zwei Signaturen über dieselben Daten sehen also verschieden aus und sind trotzdem beide gültig. HMAC, RSASSA-PKCS1-v1_5 und Ed25519 sind deterministisch.
- Vergleich: Prüfe Signaturen immer mit
crypto.subtle.verify()und vergleiche keine Strings selbst. So vermeidest du Timing-Angriffe, und bei HMAC arbeitet der Vergleich in konstanter Zeit. - Ed25519 wird erst seit Kurzem überall unterstützt (Chrome 137, Firefox 129, Safari 17). Prüfe vorher, ob es verfügbar ist.
- Kein Schutz im Client: Ein HMAC-Geheimnis im Browser-Code ist öffentlich. Signiere mit geheimen Schlüsseln nur auf dem Server oder mit nicht exportierbaren Schlüsseln (
extractable: false), die in IndexedDB gespeichert sind.