Bei JWK vs. PEM geht es darum, wohin ein Schlüssel soll. JSON Web Keys (RFC 7517) sind die Sprache von JOSE, OAuth und WebCrypto; OpenSSL, TLS-Server, SSH-Tools, Java und die meiste PKI-Software erwarten dagegen PEM-Dateien (die Textkodierung von DER-Strukturen wie SubjectPublicKeyInfo und PKCS #8 nach RFC 7468). Der kryptografische Inhalt ist identisch, die Wahl hängt also von den Tools und den Metadaten ab.
Auf einen Blick
| JWK (RFC 7517) | PEM (RFC 7468) | |
|---|---|---|
| Kodierung | JSON-Objekt mit base64url-kodierten Parametern | base64 von DER zwischen -----BEGIN …----- und -----END …----- |
| Aufbau des öffentlichen Schlüssels | kty plus typspezifische Parameter (crv, x, y, n, e) | SubjectPublicKeyInfo (BEGIN PUBLIC KEY) |
| Privater Schlüssel | Dasselbe Objekt plus d (und die RSA-CRT-Parameter) | PKCS #8 (BEGIN PRIVATE KEY) oder die älteren Formen PKCS #1 und SEC 1 |
| Metadaten | kid, alg, use, key_ops, x5c, x5t#S256 | Keine (nur Labels) |
| Sammlungen | JWK-Set: Array "keys" | Mehrere PEM-Blöcke in einer Datei |
| Größe, öffentlicher P-256-Schlüssel (gemessen) | 126 Zeichen JSON | 178 Zeichen |
| Hash als Kennung | JWK-Thumbprint (RFC 7638) | SPKI-Fingerabdruck mit SHA-256, Fingerabdruck des Zertifikats |
| Verwendet von | JOSE-Bibliotheken, WebCrypto jwk, OIDC jwks_uri, COSE-Konvertern | OpenSSL, nginx, Apache, Java X509EncodedKeySpec, Go x509, Python cryptography |
Was in jedem Format steckt
Ein JWK schreibt die Zahlen des Schlüssels direkt aus. Ein öffentlicher EC-Schlüssel besteht aus kty: EC, crv: P-256 und den Koordinaten x und y als base64url-Strings fester Länge; ein RSA-Schlüssel ist kty: RSA mit n und e; Ed25519- und X25519-Schlüssel sind kty: OKP mit crv und x (RFC 8037); bei privaten Schlüsseln kommt d hinzu. Optionale Parameter binden den Schlüssel an einen Zweck: alg (ein einziger Algorithmus), use (sig oder enc), kid und Verweise auf Zertifikate.
Ein öffentlicher Schlüssel im Format PEM ist die DER-Kodierung einer SubjectPublicKeyInfo: ein Algorithmusbezeichner (zum Beispiel id-ecPublicKey mit der OID von P-256) und ein Bitstring, der den Punkt enthält. Private Schlüssel sind Strukturen vom Typ PKCS #8 PrivateKeyInfo, optional verschlüsselt. PEM trägt außer seinem Label keine Metadaten, daher muss der Algorithmus, mit dem ein Schlüssel verwendet werden darf, an anderer Stelle konfiguriert werden.
Konvertierung zwischen beiden
Die Konvertierung ist verlustfrei. Das folgende Skript, ausgeführt mit Node.js 26.10, wandelt einen öffentlichen P-256-Schlüssel von PEM in JWK und zurück um und berechnet beide Arten von Kennungen.
import { createHash, createPublicKey, generateKeyPairSync } from "node:crypto";
const { publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
// PEM (SubjectPublicKeyInfo) -> JWK -> PEM again.
const pem = publicKey.export({ type: "spki", format: "pem" });
const jwk = publicKey.export({ format: "jwk" });
const roundTrip = createPublicKey({ key: jwk, format: "jwk" }).export({ type: "spki", format: "pem" });
// JWK thumbprint (RFC 7638): SHA-256 over the required members in lexicographic order.
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
const thumbprint = createHash("sha256").update(canonical).digest("base64url");
// SPKI fingerprint: SHA-256 over the DER-encoded SubjectPublicKeyInfo.
const spki = createHash("sha256").update(publicKey.export({ type: "spki", format: "der" })).digest("base64url");
console.log("JWK members:", Object.keys(jwk).join(", "));
console.log("PEM length:", pem.length, "JWK JSON length:", JSON.stringify(jwk).length);
console.log("PEM -> JWK -> PEM identical:", pem === roundTrip);
console.log("JWK thumbprint equals SPKI hash:", thumbprint === spki);
JWK members: kty, x, y, crv
PEM length: 178 JWK JSON length: 126
PEM -> JWK -> PEM identical: true
JWK thumbprint equals SPKI hash: false
Die letzte Zeile überrascht häufig: Ein JWK-Thumbprint ist ein Hash über kanonisches JSON, ein SPKI-Fingerabdruck ein Hash über DER, also hat derselbe Schlüssel zwei verschiedene „Fingerabdrücke“. Systeme müssen sich darauf einigen, welchen sie meinen: cnf.jkt in DPoP ist zum Beispiel ein JWK-Thumbprint, während Zertifikats-Pinning SPKI-Hashes verwendet.
Fallstricke
- Padding und Länge. JWK-Werte sind base64url ohne Padding, und EC-Koordinaten müssen ihre volle Länge behalten (32 Byte bei P-256, 66 bei P-521), einschließlich führender Nullbytes. Selbst geschriebene Konverter, die Nullen entfernen, erzeugen Schlüssel, die bei P-256 und P-384 in etwa einem von 128 Fällen scheitern, weil
xundyjeweils etwa in einem von 256 Fällen mit einem Nullbyte beginnen; bei P-521, wo das oberste Byte nur ein einziges Bit trägt, scheitern etwa drei von vier Schlüsseln; Node meldet ungültige JWKs, und WebCrypto wirft einenDataError. - Private Schlüssel an öffentlichen Orten. Ein JWK mit
dist ein privater Schlüssel. Veröffentlichst du ihn in einem JWKS oder fügst ihn in ein Tool ein, liegt der Schlüssel offen; die Tools dieser Website lehnen JWKs mitdab, wo nur ein öffentlicher Schlüssel gebraucht wird. - Falsches PEM-Label.
BEGIN RSA PUBLIC KEY(PKCS #1) undBEGIN PUBLIC KEY(SPKI) sind verschiedene Strukturen; viele JWK-Konverter akzeptieren nur SPKI. - Verlorene Metadaten. Bei der Konvertierung eines JWK in PEM gehen
kidundalgverloren; halte sie in der Konfiguration fest, damit Verifizierer den Algorithmus weiterhin festlegen. - Private Ed25519-Schlüssel.
dim JWK ist der 32-Byte-Seed; die rohen privaten Schlüssel mancher Bibliotheken sind 64 Byte lang (Seed plus öffentlicher Schlüssel). - Zertifikate sind keine Schlüssel. Ein JWK kann in
x5cauf eine Zertifikatskette verweisen, doch diese Einträge sind DER-Zertifikate in normalem base64, nicht in base64url, und Verifizierer müssen die Kette prüfen, bevor sie dem enthaltenen Schlüssel vertrauen; ein PEM-BlockBEGIN CERTIFICATEenthält ebenfalls einen öffentlichen Schlüssel, aber ihn zu extrahieren (openssl x509 -pubkey -noout) ist ein anderer Schritt, als ihm zu vertrauen.
Wann welches Format passt
- Prüfschlüssel für Tokens, die ein Aussteller veröffentlicht: JWK-Set unter
jwks_uri. - Schlüssel für JOSE-Bibliotheken oder WebCrypto im Browser: JWK (WebCrypto akzeptiert auch SPKI und PKCS #8 als DER).
- Schlüssel und Zertifikate von TLS-Servern, CSRs, OpenSSL-Workflows: PEM.
- Code in Java, .NET und Go mit den Standardklassen für Schlüssel: PEM oder DER (SPKI und PKCS #8); JWK wird über JOSE-Bibliotheken unterstützt.
- Konfigurationsdateien und Secrets-Manager: beides; PEM wird von mehr Tools unterstützt, JWK hält die Metadaten beim Schlüssel.
- Pinning und Schlüsselkennungen: Wähle eine Hash-Definition (JWK-Thumbprint oder SPKI mit SHA-256) und dokumentiere sie.
Hinweise zur Migration
- Erzeuge Schlüssel in dem Speicher, der sie aufbewahren soll, und exportiere dann den öffentlichen Teil in dem Format, das jeder Abnehmer braucht; wandle private Schlüssel nicht öfter als nötig in andere Formate um.
- Konvertiere mit Bibliotheken:
node:crypto(createPublicKeyundexport), in Pythoncryptographymitjwcryptooderjoserfc,josein JavaScript, Nimbus in Java oder der JWK-Konverter, der lokal läuft. - Setze
kid,algunduseexplizit, wenn du JWKs für ein JWKS erstellst; leitekidaus dem JWK-Thumbprint ab, wenn du stabile Kennungen willst. - Prüfe nach der Konvertierung: Signiere mit dem privaten Schlüssel und prüfe mit dem konvertierten öffentlichen Schlüssel, bevor du veröffentlichst.
- Halte beide Darstellungen bei der Rotation synchron, denn sonst können sich ein JWKS und ein per PEM konfiguriertes Gateway über den aktuellen Schlüssel uneinig sein.