Signatur
Beschreibung
exportKey() wandelt einen Schlüssel, der als undurchsichtiges CryptoKey-Objekt vorliegt, in eine speicher- oder übertragbare Form um. So lassen sich Schlüssel an einen Server senden, in localStorage bzw. IndexedDB ablegen, als PEM-Datei weitergeben oder mit anderen Systemen (z. B. OpenSSL, Node.js, JOSE-Bibliotheken) austauschen. Das Gegenstück ist SubtleCrypto.importKey().
Unterstützt werden vier Formate:
"raw"– rohe Schlüsselbytes; für symmetrische Schlüssel (AES, HMAC) sowie öffentliche Schlüssel von ECDSA/ECDH/Ed25519/X25519."pkcs8"– DER-kodierter privater Schlüssel (RSA, EC, Ed25519, X25519)."spki"– DER-kodierter öffentlicher Schlüssel (SubjectPublicKeyInfo)."jwk"– JSON Web Key als JavaScript-Objekt; für fast alle Schlüsseltypen.
Ein Schlüssel kann nur exportiert werden, wenn seine Eigenschaft extractable auf true steht. Diese wird beim Erzeugen (generateKey()) oder Importieren (importKey()) festgelegt. Öffentliche Schlüssel eines Schlüsselpaars sind immer exportierbar, unabhängig vom übergebenen Wert.
Für das verschlüsselte Exportieren (Key Wrapping) gibt es SubtleCrypto.wrapKey(), das intern exportiert und das Ergebnis mit einem weiteren Schlüssel verschlüsselt.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $format Pflicht | "raw" | "pkcs8" | "spki" | "jwk" | Zielformat des Exports. Muss zum Schlüsseltyp passen (z. B. "pkcs8" nur für private, "spki" nur für öffentliche Schlüssel). |
|
| $key Pflicht | CryptoKey | Der zu exportierende Schlüssel. Muss extractable: true besitzen (außer bei öffentlichen Schlüsseln). |
Rückgabewert
"jwk" mit einem JsonWebKey-Objekt und bei allen anderen Formaten mit einem ArrayBuffer erfüllt wird. Es wird mit InvalidAccessError abgelehnt, wenn der Schlüssel nicht exportierbar ist, und mit NotSupported bzw. NotSupportedError, wenn das Format nicht zum Schlüssel passt.Beispiele
AES-Schlüssel im Rohformat exportieren
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // extractable – sonst schlägt exportKey fehl
["encrypt", "decrypt"]
);
const raw = await crypto.subtle.exportKey("raw", key);
const bytes = new Uint8Array(raw);
// Als Hex-String darstellen
const hex = [...bytes].map(b => b.toString(16).padStart(2, "0")).join("");
console.log(bytes.length, hex.length);
Öffentlichen RSA-Schlüssel als PEM (SPKI) exportieren
const { publicKey } = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
const spki = await crypto.subtle.exportKey("spki", publicKey);
// ArrayBuffer -> Base64 -> PEM
const base64 = btoa(String.fromCharCode(...new Uint8Array(spki)));
const pem = [
"-----BEGIN PUBLIC KEY-----",
...base64.match(/.{1,64}/g),
"-----END PUBLIC KEY-----"
].join("\n");
console.log(pem.split("\n")[0]);
ECDSA-Schlüssel als JWK exportieren und an den Server senden
const { publicKey, privateKey } = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign", "verify"]
);
const jwk = await crypto.subtle.exportKey("jwk", publicKey);
console.log(jwk.kty, jwk.crv, Object.keys(jwk).sort().join(","));
// Nur den öffentlichen Schlüssel übertragen!
await fetch("/api/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(jwk)
});
Fehler bei nicht exportierbarem Schlüssel abfangen
const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-256" },
false, // nicht exportierbar
["sign", "verify"]
);
try {
await crypto.subtle.exportKey("raw", key);
} catch (err) {
console.log(err.name);
}
// Wichtig · Fallstricke
- Sicherer Kontext:
crypto.subtlegibt es nur in sicheren Kontexten (HTTPS oderlocalhost); sonst ist esundefined. - Sicherheit: Setze
extractablenur dann auftrue, wenn du wirklich exportieren musst. Nicht exportierbare Schlüssel lassen sich trotzdem in IndexedDB speichern (per Structured Clone), und ein XSS-Angreifer kann das Schlüsselmaterial dann nicht auslesen. Private Schlüssel im Klartext niemals inlocalStorageablegen oder unverschlüsselt übertragen – nutze dafürwrapKey(). - Formatwahl:
"pkcs8"auf einen öffentlichen oder"spki"auf einen privaten Schlüssel führt zu einemInvalidAccessError."raw"funktioniert nicht für RSA-Schlüssel und nicht für private EC-Schlüssel. - PEM: Die API liefert DER-Binärdaten, kein PEM. Kopf-/Fußzeilen und Base64-Kodierung musst du selbst hinzufügen.
String.fromCharCode(...bytes)kann bei sehr großen Puffern den Aufrufstapel sprengen – dann blockweise umwandeln. - JWK: Das Ergebnis enthält Felder wie
extundkey_ops, die bei einem späterenimportKey()berücksichtigt werden. Ein privater JWK enthält immer auch die öffentlichen Anteile. - Kompatibilität: Wird in allen modernen Browsern unterstützt. Ed25519/X25519 sind erst in neueren Browserversionen verfügbar. In Node.js steht die Methode über
globalThis.crypto.subtle(ab Node 19 global) bzw.require("node:crypto").webcrypto.subtlebereit.