Signatur
Beschreibung
crypto.subtle.verify() ist das Gegenstück zu crypto.subtle.sign(). Die Methode prüft, ob eine Signatur mit dem angegebenen Schlüssel über genau diese Daten erzeugt wurde. Bei asymmetrischen Verfahren (RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, Ed25519) brauchen Sie dafür den öffentlichen Schlüssel. Bei HMAC verwenden Sie denselben geheimen Schlüssel, mit dem auch signiert wurde.
Typische Anwendungen sind die Prüfung von JWTs oder signierten Webhook-Nachrichten im Browser, in Service Workern oder in Edge-Runtimes. Auch die Echtheit heruntergeladener Daten oder signierter Konfigurationen lässt sich so prüfen, ohne eine externe Kryptobibliothek.
Das Ergebnis ist ein Promise, das mit true (Signatur gültig) oder false (ungültig) erfüllt wird. Eine ungültige Signatur löst keinen Fehler aus. Abgelehnt wird das Promise nur bei falscher Benutzung, etwa wenn der Schlüssel nicht zum Algorithmus passt oder nicht für "verify" freigegeben ist.
Die Parameter für den Algorithmus müssen genau denen beim Signieren entsprechen. Das gilt z. B. für die Hash-Funktion bei ECDSA und die saltLength bei RSA-PSS. Sonst schlägt die Prüfung stillschweigend mit false fehl.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $algorithm Pflicht | string | AlgorithmIdentifier | RsaPssParams | EcdsaParams | Das Verfahren: "RSASSA-PKCS1-v1_5", { name: "RSA-PSS", saltLength }, { name: "ECDSA", hash }, "HMAC" oder "Ed25519". Es muss zum Signaturvorgang passen. |
|
| $key Pflicht | CryptoKey | Bei asymmetrischen Verfahren der öffentliche Schlüssel, bei HMAC der geheime Schlüssel. Der Schlüssel muss "verify" in usages enthalten. |
|
| $signature Pflicht | ArrayBuffer | TypedArray | DataView | Die zu prüfende Signatur als Binärdaten. | |
| $data Pflicht | ArrayBuffer | TypedArray | DataView | Die ursprünglichen Daten, über die signiert wurde. Text vorher z. B. mit TextEncoder in Bytes umwandeln. |
Rückgabewert
true erfüllt, wenn die Signatur gültig ist, sonst mit false. Abgelehnt wird es mit InvalidAccessError, wenn der Schlüssel nicht zum Algorithmus passt oder nicht für die Prüfung freigegeben ist, und mit NotSupportedError bei einem unbekannten Algorithmus.Beispiele
ECDSA: Nachricht signieren und prüfen
const encoder = new TextEncoder();
const data = encoder.encode('Bestellung #4711: 3 Artikel');
const { privateKey, publicKey } = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
const algo = { name: 'ECDSA', hash: 'SHA-256' };
const signature = await crypto.subtle.sign(algo, privateKey, data);
const ok = await crypto.subtle.verify(algo, publicKey, signature, data);
console.log('Gültig:', ok);
// Manipulierte Daten
const tampered = encoder.encode('Bestellung #4711: 30 Artikel');
console.log('Manipuliert gültig:', await crypto.subtle.verify(algo, publicKey, signature, tampered));
HMAC-Webhook-Signatur prüfen (Hex-Signatur)
const hexToBytes = (hex) =>
new Uint8Array(hex.match(/.{2}/g).map((b) => parseInt(b, 16)));
const verifyWebhook = async (secret, body, signatureHex) => {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
return crypto.subtle.verify('HMAC', key, hexToBytes(signatureHex), enc.encode(body));
};
// Zum Testen: Signatur zuerst selbst erzeugen
const enc = new TextEncoder();
const signKey = await crypto.subtle.importKey(
'raw', enc.encode('geheim'), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const body = '{"event":"payment.succeeded"}';
const sig = new Uint8Array(await crypto.subtle.sign('HMAC', signKey, enc.encode(body)));
const sigHex = [...sig].map((b) => b.toString(16).padStart(2, '0')).join('');
console.log(await verifyWebhook('geheim', body, sigHex));
console.log(await verifyWebhook('falsch', body, sigHex));
JWT (RS256) mit öffentlichem JWK prüfen
const b64urlToBytes = (s) => {
const b64 = s.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(s.length / 4) * 4, '=');
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
};
const verifyJwtRS256 = async (token, publicJwk) => {
const [header, payload, sig] = token.split('.');
const key = await crypto.subtle.importKey(
'jwk',
publicJwk,
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
false,
['verify']
);
const valid = await crypto.subtle.verify(
'RSASSA-PKCS1-v1_5',
key,
b64urlToBytes(sig),
new TextEncoder().encode(`${header}.${payload}`)
);
if (!valid) throw new Error('Ungültige Signatur');
return JSON.parse(new TextDecoder().decode(b64urlToBytes(payload)));
};
// Aufruf: const claims = await verifyJwtRS256(token, jwkVomServer);
// Wichtig · Fallstricke
- Nur in sicheren Kontexten:
crypto.subtlegibt es nur über HTTPS oder auflocalhost. Auf unsicheren Seiten ist esundefined. - Kein Fehler bei ungültiger Signatur: Prüfen Sie immer den booleschen Wert. Ein bloßes
awaitohne Auswertung heißt nicht, dass die Signatur gültig ist. - Parameter müssen übereinstimmen: Hash,
saltLength(RSA-PSS) und Kurve müssen dieselben sein wie beim Signieren. Bei RSA ist der Hash an den Schlüssel gebunden und wird inimportKeyfestgelegt. Bei ECDSA wird er imalgorithm-Objekt vonverify()angegeben. - Signaturformat bei ECDSA: Web Crypto erwartet das rohe Format
r||s(IEEE P1363), bei P-256 also 64 Byte. OpenSSL, Java und Node-crypto.signerzeugen standardmäßig DER-kodierte Signaturen. Diese müssen Sie erst umwandeln, sonst ist das Ergebnis immerfalse. - HMAC ohne eigenen Vergleich: Verwenden Sie
verify(), statt selbst zu signieren und die Bytes zu vergleichen.verify()vergleicht in konstanter Zeit, ein eigener Vergleich mit===kann für Timing-Angriffe anfällig sein. - Text immer zuerst in Bytes umwandeln (
TextEncoder). Schon Unterschiede bei Leerzeichen, Zeilenenden oder in der JSON-Serialisierung machen die Signatur ungültig. Prüfen Sie daher immer den rohen Anfrage-Body. - Ed25519 ist erst seit Kurzem in allen großen Browsern verfügbar (Chrome 137, Firefox 129, Safari 17). Prüfen Sie die Unterstützung, bevor Sie es einsetzen.
- Der Schlüssel muss
"verify"in seinenusageshaben, sonst wird das Promise mitInvalidAccessErrorabgelehnt.