Signatur
Beschreibung
SubtleCrypto ist der Kern der Web Crypto API. Man kann die Schnittstelle nicht selbst instanziieren. Stattdessen greift man über crypto.subtle auf eine vorhandene Instanz zu, im Browser über window.crypto.subtle bzw. in Workern über self.crypto.subtle. Die Operationen laufen nativ und sind damit deutlich schneller und sicherer als Kryptografie-Bibliotheken in reinem JavaScript.
Alle Methoden arbeiten asynchron und geben ein Promise zurück. Die wichtigsten Methoden sind:
- Hashing:
digest()(SHA-1, SHA-256, SHA-384, SHA-512) - Verschlüsselung:
encrypt()unddecrypt()(AES-GCM, AES-CBC, AES-CTR, RSA-OAEP) - Signaturen:
sign()undverify()(HMAC, ECDSA, RSA-PSS, RSASSA-PKCS1-v1_5, Ed25519) - Schlüsselverwaltung:
generateKey(),importKey(),exportKey(),deriveKey(),deriveBits()(PBKDF2, HKDF, ECDH, X25519) - Schlüssel verpacken:
wrapKey()undunwrapKey()
Schlüssel werden als CryptoKey-Objekte übergeben. Deren Eigenschaft extractable legt fest, ob sich der Schlüssel exportieren lässt. usages legt fest, wofür er verwendet werden darf. Daten werden immer als ArrayBuffer, TypedArray oder DataView übergeben. Texte wandelt man vorher mit TextEncoder um.
Der Name „subtle“ ist eine bewusste Warnung: Die API ist mächtig, lässt sich aber leicht falsch bedienen. Typische Fehler sind ein wiederverwendeter Initialisierungsvektor (IV), veraltete Algorithmen oder zu wenige PBKDF2-Iterationen. Wer eigene Protokolle entwirft, sollte das nur mit fundiertem Kryptografie-Wissen tun.
Rückgabewert
crypto.subtle. Alle Methoden geben ein Promise zurück, das je nach Methode mit einem ArrayBuffer, CryptoKey, CryptoKeyPair, boolean oder JsonWebKey erfüllt wird.Beispiele
SHA-256-Hash eines Textes als Hex-String
const sha256 = async (text) => {
const data = new TextEncoder().encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return [...new Uint8Array(hashBuffer)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
};
console.log(await sha256('hallo'));
Daten mit AES-GCM ver- und entschlüsseln
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true, // exportierbar
['encrypt', 'decrypt']
);
// Für jede Verschlüsselung einen neuen, zufälligen IV verwenden!
const iv = crypto.getRandomValues(new Uint8Array(12));
const klartext = new TextEncoder().encode('Geheime Nachricht');
const chiffrat = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, klartext);
const entschluesselt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, chiffrat);
console.log(new TextDecoder().decode(entschluesselt));
HMAC-Signatur erstellen und prüfen (z. B. für Webhooks)
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode('mein-geheimer-schluessel'),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign', 'verify']
);
const payload = enc.encode(JSON.stringify({ id: 42, status: 'paid' }));
const signatur = await crypto.subtle.sign('HMAC', key, payload);
const gueltig = await crypto.subtle.verify('HMAC', key, signatur, payload);
console.log('Signatur gültig:', gueltig);
Schlüssel aus einem Passwort ableiten (PBKDF2 → AES-GCM)
const passwortZuSchluessel = async (passwort, salt) => {
const basis = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(passwort),
'PBKDF2',
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600_000, hash: 'SHA-256' },
basis,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
};
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = await passwortZuSchluessel('Correct-Horse-Battery', salt);
console.log(key.algorithm, key.usages);
// Wichtig · Fallstricke
- Nur im sicheren Kontext:
crypto.subtlegibt es nur unter HTTPS oderlocalhost. Unter reinem HTTP ist der Wertundefined. Das ist eine häufige Ursache für den Fehler „Cannot read properties of undefined“. - IV bzw. Nonce nie wiederverwenden: Wird bei AES-GCM derselbe IV mit demselben Schlüssel mehrfach verwendet, ist die Sicherheit komplett gebrochen. Erzeugen Sie für jede Nachricht einen neuen IV mit
crypto.getRandomValues(). Den IV speichern Sie zusammen mit dem Chiffrat, geheim ist er nicht. - Veraltete Algorithmen meiden: SHA-1 und AES-CBC ohne Authentifizierung sollten Sie nicht mehr verwenden. RSASSA-PKCS1-v1_5 nur einsetzen, wenn die Kompatibilität es erfordert. Für Passwort-Hashing ist
digest()ungeeignet. Nutzen Sie PBKDF2 mit vielen Iterationen oder serverseitig Argon2 bzw. bcrypt. - Fehler bei der Entschlüsselung: Mit falschem Schlüssel, falschem IV oder manipulierten Daten wird das Promise von
decrypt()mit einerOperationError-DOMExceptionabgelehnt. Die Meldung ist bewusst unspezifisch. - Nicht exportierbare Schlüssel: Bei
extractable: falseverlässt der Rohschlüssel die Engine nicht. AlsCryptoKeylässt er sich aber in IndexedDB speichern. Das ist gut geeignet für clientseitige Schlüssel. - Kompatibilität: Die Grundfunktionen werden in allen modernen Browsern unterstützt. Ed25519 und X25519 sind erst in neueren Versionen verfügbar (Chrome 137+, Firefox 129+, Safari 17+). In Node.js ist die API über
require('node:crypto').webcrypto.subtleerreichbar, ab Node.js 19 auch global alscrypto.subtle. - Clientseitige Kryptografie ersetzt kein HTTPS: Wer den ausgelieferten JavaScript-Code manipulieren kann, etwa durch XSS, kann auch die Kryptografie umgehen. Eine strenge CSP und Subresource Integrity sind daher weiterhin wichtig.