Bei JWS vs. JWE geht es darum, was ein JOSE-Token schützt. Eine JSON Web Signature (RFC 7515) versieht eine Payload, die lesbar bleibt, mit Integrität und Authentizität; eine JSON Web Encryption (RFC 7516) sorgt für Vertraulichkeit. Fast jedes JWT, das dir begegnet, ist ein JWS – das überrascht viele, die erwarten, dass die Claims geheim sind.
Auf einen Blick
| JWS | JWE | |
|---|---|---|
| Spezifikation | RFC 7515 | RFC 7516 |
| Schützt | Integrität und Herkunft | Vertraulichkeit (und Integrität über das AEAD) |
| Kompakte Form | header.payload.signature (3 Teile) | header.encrypted_key.iv.ciphertext.tag (5 Teile) |
| Algorithmen im Header | alg (zum Beispiel ES256, RS256, HS256, Ed25519) | alg für das Schlüsselmanagement (ECDH-ES, ECDH-ES+A256KW, RSA-OAEP-256, A256KW, dir) plus enc für den Inhalt (A256GCM, A128CBC-HS256) |
| Payload sichtbar | Ja, nur base64url | Nein |
| Absender authentifiziert | Ja (Signatur oder MAC) | Nur mit symmetrischer oder authentifizierter Schlüsseleinigung; nicht mit ECDH-ES oder RSA-OAEP allein |
| Größe bei 70 Byte Claims (unten gemessen) | 186 Zeichen (ES256) | 405 Zeichen (ECDH-ES+A256KW mit A256GCM) |
| Typische Einsätze | Access Tokens und ID-Tokens als JWT, DPoP, signierte Anfragen, SD-JWT | Verschlüsselte ID-Tokens, clientseitig gespeicherte Tokens, einige Antworten in FAPI und EUDI |
Was jedes Format schützt
Ein JWS signiert base64url(header) + "." + base64url(payload). Wer das Token hat, kann die Claims dekodieren und lesen; der JWT-Debugger macht genau das. Das Signieren beweist, dass das Token vom Inhaber des Schlüssels stammt und nicht verändert wurde – genau das braucht eine API, um Claims zu vertrauen.
Ein JWE erzeugt einen zufälligen Schlüssel für die Inhaltsverschlüsselung (Content Encryption Key, CEK), verschlüsselt die Payload mit dem in enc genannten AEAD und übermittelt den CEK dem Empfänger mit dem in alg genannten Algorithmus für das Schlüsselmanagement: mit RSA-OAEP verpackt, nach einer Schlüsseleinigung per ECDH-ES mit AES Key Wrap verpackt, direkt aus ECDH-ES abgeleitet oder vorab geteilt (dir). Der geschützte Header wird als zugehörige Daten (Associated Data) authentifiziert. Nur der Empfänger kann die Payload lesen.
Die entscheidende Asymmetrie: Beim Schlüsselmanagement mit Public-Key-Verfahren kann jeder, der den öffentlichen Schlüssel des Empfängers hat, ein gültiges JWE erzeugen. Eine erfolgreiche Entschlüsselung beweist nichts über den Absender. Deshalb werden sensible Tokens, denen der Empfänger auch vertrauen muss, verschachtelt: Ein signiertes JWT (JWS) steckt in einem JWE mit cty: JWT und wird nach der Entschlüsselung geprüft.
Größen in der Praxis
Das folgende Skript, ausgeführt mit jose 6.2.13 unter Node.js 26.10, schützt dieselben Claims auf beide Arten.
import { CompactEncrypt, CompactSign, compactDecrypt, compactVerify, generateKeyPair } from "jose";
const claims = new TextEncoder().encode(JSON.stringify({ sub: "user-42", email: "alice@example.com", plan: "pro" }));
// JWS: integrity and origin; the payload is only base64url-encoded.
const signer = await generateKeyPair("ES256");
const jws = await new CompactSign(claims).setProtectedHeader({ alg: "ES256" }).sign(signer.privateKey);
await compactVerify(jws, signer.publicKey);
// JWE: confidentiality for the holder of the recipient's private key.
const recipient = await generateKeyPair("ECDH-ES+A256KW");
const jwe = await new CompactEncrypt(claims).setProtectedHeader({ alg: "ECDH-ES+A256KW", enc: "A256GCM" }).encrypt(recipient.publicKey);
const { plaintext } = await compactDecrypt(jwe, recipient.privateKey);
console.log("JWS parts:", jws.split(".").length, "length:", jws.length, "payload readable:", Buffer.from(jws.split(".")[1], "base64url").toString().includes("alice"));
console.log("JWE parts:", jwe.split(".").length, "length:", jwe.length, "decrypts:", new TextDecoder().decode(plaintext).includes("alice"));
JWS parts: 3 length: 186 payload readable: true
JWE parts: 5 length: 405 decrypts: true
Der Großteil des Overheads beim JWE entfällt auf den ephemeren öffentlichen Schlüssel (epk) im Header, den verpackten Schlüssel, den 12-Byte-IV und das 16-Byte-Tag. Ein verschachteltes, erst signiertes und dann verschlüsseltes Token wird noch größer, weil das ganze JWS zum Klartext des JWE wird.
Fallstricke
- JWS-Claims für geheim halten. Schreibe keine personenbezogenen Daten, keine internen Kennungen, die du nicht veröffentlichen würdest, und keine Secrets in ein nur signiertes Token; es landet in Logs, im Browser-Speicher und in Support-Tickets.
- Einem unsignierten JWE vertrauen. Die Entschlüsselung mit ECDH-ES oder RSA-OAEP beweist nur, dass das Token für dich verschlüsselt wurde.
- Schwache oder veraltete JWE-Algorithmen.
RSA1_5ist anfällig für Padding-Oracle-Angriffe,PBES2mit einer riesigen Iterationszahl inp2clässt sich für Denial-of-Service-Angriffe missbrauchen, und Implementierungen vonECDH-ES, die Punkte außerhalb der Kurve akzeptierten, gaben 2017 private Schlüssel preis (RFC 8725 §2.5). Nimm bevorzugtECDH-ES+A256KWoderRSA-OAEP-256mitA256GCM, und begrenze die PBES2-Iterationen, wenn du PBES2 unterstützen musst. - Algorithmuswahl durch das Token. Wie bei JWS sollte der Empfänger für jeden Schlüssel nur die Werte von
algundencakzeptieren, die er erwartet. - Kompression.
zip: DEFin JWE komprimiert vor dem Verschlüsseln, wodurch über die Länge Informationen über den Klartext durchsickern können (RFC 8725 §3.6); verzichte darauf.
Wann welches Format passt
- Access Tokens, die deine APIs prüfen: JWS; halte die Claims minimal, oder nutze opake Tokens mit Introspection, wenn sie nicht lesbar sein dürfen.
- ID-Tokens mit personenbezogenen Daten, die ein Browser oder eine mobile App speichert: JWS ist üblich; ein verschachteltes JWE, wenn dein Bedrohungsmodell die Client-Umgebung einschließt.
- Tokens, die über nicht vertrauenswürdige Zwischenstationen laufen (Weiterleitungen über Dritte, in URLs oder QR-Codes eingebettete Tokens): ein verschachteltes JWS in einem JWE.
- Machine-to-Machine-Nachrichten über gegenseitig authentifiziertes TLS: oft keines von beiden; TLS sorgt auf dem Transportweg bereits für Vertraulichkeit und Integrität, und ein JWS bringt nur dann etwas, wenn die Nachricht prüfbar bleiben muss, nachdem sie gespeichert oder weitergeleitet wurde.
- Ende-zu-Ende-verschlüsselte Payloads an einen bekannten Empfänger: JWE mit
ECDH-ES+A256KWundA256GCModer HPKE-basierte Formate, sobald sie für JOSE standardisiert sind.
Hinweise zur Migration
- Ordne die Claims in bestehenden JWS-Tokens ein: Alles, was für den Inhaber nicht sichtbar sein darf, verlässt entweder das Token oder wandert in ein JWE.
- Führe verschachtelte Tokens schrittweise ein: Stelle JWEs mit
cty: JWTfür Clients aus, die sie entschlüsseln können, und lass das innere JWS unverändert, damit die bestehende Logik zur Prüfung weiter genutzt wird. - Verteile Schlüssel für die Verschlüsselung getrennt von Signaturschlüsseln: JWKS-Einträge sollten
use: encoderuse: sigund unterschiedliche Werte fürkidhaben. - Lege die Algorithmen auf beiden Seiten fest und lehne
RSA1_5,PBES2ohne Obergrenze und Kompression ab. - Prüfe die Protokollierung erneut: Sobald Tokens verschlüsselt sind, stelle sicher, dass entschlüsselte Claims nicht dort protokolliert werden, wo das Token selbst nie auftauchte. Der JWE-Inspektor zeigt Header und Struktur eines JWE, ohne es zu entschlüsseln.
Quellen
- RFC 7515, JSON Web Signature
- RFC 7516, JSON Web Encryption
- RFC 7518 §4 und §5, Algorithmen für Schlüsselmanagement und Inhaltsverschlüsselung
- RFC 7519 §11.2, Reihenfolge von Signieren und Verschlüsseln (verschachtelte JWTs)
- RFC 8725, Best Current Practices für JWTs
- Dokumentation von jose (JavaScript)