Zum Inhalt springen
ecdsa.com

JWT vs. PASETO: flexibler Standard oder feste Kryptografie

PASETO verzichtet auf den alg-Header und auf die Wahlmöglichkeiten, die die meisten JWT-Schwachstellen verursacht haben. JWT behält das Ökosystem: Identity Provider, Standards und Bibliotheken in jeder Sprache.

JWT vs. PASETO ist eine Abwägung zwischen Flexibilität und Schutz vor Fehlanwendung. JWT (RFC 7519, meist ein JWS) lässt das Token seinen Algorithmus aus einer langen Registry nennen, was klassische Bugs wie alg: none und die Verwechslung von RS256 mit HS256 ermöglichte. PASETO (Platform-Agnostic SEcurity TOkens) legt die Kryptografie pro Protokollversion fest, sodass ein v4-Token vom Typ public immer Ed25519 verwendet und ein v4-Token vom Typ local immer XChaCha20 mit BLAKE2b.

Auf einen Blick

JWT (JWS)PASETO v4
SpezifikationRFC 7519 mit RFC 7515/7518 (IETF-Standards)Community-Spezifikation (paseto-standard/paseto-spec); IETF-Entwürfe abgelaufen
Formatbase64url(header).base64url(claims).base64url(signature)v4.public. + base64url(Nachricht ‖ Signatur), optional .footer
AlgorithmuswahlHeader alg aus der IANA-RegistryDurch Version und Zweck festgelegt
Signaturen mit öffentlichem SchlüsselES256, RS256, PS256, EdDSA/Ed25519 und weiterev4.public: nur Ed25519 (v3.public: ECDSA P-384)
Symmetrische TokensHS256 (nur MAC, lesbare Claims) oder JWEv4.local: XChaCha20 + BLAKE2b-MAC (verschlüsselt und authentifiziert)
none und Algorithm ConfusionMöglich bei falsch konfiguriertem VerifiziererNicht ausdrückbar
Implizite Assertions (authentifiziert, nicht übertragen)NeinJa (v3 und v4)
Beispielgröße (unten gemessen)227 Zeichen280 Zeichen
SchlüsselformatJWK und JWKSPASERK (k4.public…, k4.local…)
ÖkosystemJeder IdP, OAuth/OIDC, Gateways, Cloud-DiensteBibliotheken in vielen Sprachen; kaum Unterstützung durch IdPs oder Gateways

Unterschiede im Design

Wer ein JWT prüft, sieht einen Header, der den Algorithmus nennt, und muss entscheiden, ob er ihm vertraut. RFC 7518 registriert über ein Dutzend Signatur- und MAC-Algorithmen plus none, und Bibliotheken verzweigten früher anhand des Headers. Das führte 2015 zu einer Welle von Bugs: Unsignierte Tokens wurden akzeptiert, und öffentliche RSA-Schlüssel dienten als HMAC-Secrets (siehe HS256 vs. RS256). RFC 8725 behebt das mit Konfigurationsregeln, die jede moderne Bibliothek unterstützt, anwenden muss sie aber jedes System selbst.

PASETO ersetzt die Aushandlung durch Versionen. Jede Version legt pro Zweck ein Primitiv fest: v4 nutzt Ed25519 für Tokens vom Typ public und XChaCha20-Verschlüsselung mit einem schlüsselabhängigen BLAKE2b-MAC für Tokens vom Typ local; v3 nutzt NIST-Algorithmen (ECDSA P-384, AES-256-CTR mit HMAC-SHA-384) für Umgebungen, die sie verlangen. Ein Verifizierer, der für v4-Tokens vom Typ public konfiguriert ist, lässt sich zu nichts anderem verleiten, und Schlüssel sind typisiert, sodass sich ein Schlüssel für local nicht als Schlüssel für public verwenden lässt. Das Pre-Authentication Encoding (PAE) bindet Version, Zweck, Payload, Footer und optionale implizite Assertions, etwa eine Mandantenkennung, die beide Seiten kennen, die das Token aber nicht mitführt.

Zwei weitere Unterschiede zählen in der Praxis. Die symmetrischen Tokens vom Typ local sind bei PASETO verschlüsselt, symmetrische JWTs mit HS256 dagegen nur per MAC geschützt und lesbar; das Gegenstück zu v4.local ist bei JWT ein JWE. Außerdem hat PASETO kein Gegenstück zur JWKS-Discovery, die Schlüsselverteilung liegt also bei dir.

Direkter Vergleich

Das folgende Skript, ausgeführt unter Node.js 26.10 mit paseto 3.1.4 und jose 6.2.13, stellt dieselben Claims mit einem einzigen Ed25519-Schlüssel in beiden Formaten aus.

Derselbe Ed25519-Schlüssel und dieselben Claims als PASETO v4.public und als JWT
import { generateKeyPairSync } from "node:crypto";
import { SignJWT, jwtVerify } from "jose";
import { V4 } from "paseto";

const claims = { sub: "user-42", scope: "orders:read" };
const { privateKey, publicKey } = generateKeyPairSync("ed25519");

// PASETO v4.public: Ed25519 is implied by the version; no alg header exists.
const paseto = await V4.sign(claims, privateKey, { expiresIn: "15m", issuer: "https://auth.example.com" });
const pasetoClaims = await V4.verify(paseto, publicKey, { issuer: "https://auth.example.com" });

// JWT with the same key: the verifier must pin the algorithm itself.
const jwt = await new SignJWT(claims).setProtectedHeader({ alg: "EdDSA" }).setIssuer("https://auth.example.com").setExpirationTime("15m").sign(privateKey);
const { payload } = await jwtVerify(jwt, publicKey, { algorithms: ["EdDSA"], issuer: "https://auth.example.com" });

console.log("PASETO prefix:", paseto.slice(0, 10), "length:", paseto.length, "sub:", pasetoClaims.sub);
console.log("JWT    prefix:", jwt.slice(0, 10), "length:", jwt.length, "sub:", payload.sub);
Ausgabe
PASETO prefix: v4.public. length: 280 sub: user-42
JWT    prefix: eyJhbGciOi length: 227 sub: user-42

Das PASETO ist hier etwas länger, weil die Bibliothek iat hinzufügt und Zeitstempel nach ISO 8601 verwendet, während das JWT numerische Datumswerte nutzt; die Signaturen sind gleich groß. Beachte die Option algorithms: ["EdDSA"] auf der JWT-Seite: Genau diese eine Zeile ist mit PASETO überflüssig.

Ökosystem und Interoperabilität

JWT steckt in OAuth 2.0 (Access Tokens nach RFC 9068), in ID-Tokens von OpenID Connect, in DPoP, SD-JWT, FAPI, in Workload-Identitäten in der Cloud und in praktisch jedem API-Gateway und Identity Provider. Bibliotheken gibt es für jede Sprache, und sie werden seit Jahren auf Sicherheit geprüft.

Das Projekt PASETO Standard pflegt die Spezifikation von PASETO auf GitHub; frühere Internet-Drafts der IETF liefen ab, ohne zu RFCs zu werden. Bibliotheken gibt es für viele Sprachen, aber Qualität und Abdeckung der Versionen schwanken, Gateways und Identity Provider akzeptieren PASETO selten, und es gibt keinen standardisierten Discovery-Mechanismus. Wählst du PASETO, verantwortest du deshalb Aussteller, Verifizierer und Schlüsselverteilung selbst.

Wann welches Format passt

  • OAuth oder OpenID Connect, Empfänger bei Dritten, API-Gateways: JWT, mit einer Allowlist der Algorithmen pro Schlüssel.
  • Interne Service-Tokens in einem polyglotten System, das du in der Hand hast: beides; PASETO, wenn es für jede Sprache eine gepflegte v4-Bibliothek gibt.
  • Verschlüsselte Tokens, die Clients speichern (Sitzungsdaten in einem Cookie): PASETO v4.local ist einfacher als JWE; bei JWT nimm JWE mit dir oder A256KW und A256GCM.
  • Umgebungen mit FIPS-Vorgaben: JWT mit ES256 oder PS256 oder PASETO v3; XChaCha20 und BLAKE2b aus v4 sind nicht nach FIPS zugelassen.
  • Teams, für die Token-Sicherheit neu ist: PASETO beseitigt eine ganze Klasse von Konfigurationsfehlern; eine JWT-Bibliothek mit sicheren Standardeinstellungen (etwa jose, das verlangt, dass Algorithmen zu den Schlüsseltypen passen) verkleinert den Abstand.

Hinweise zur Migration

  1. Mische die Formate nicht hinter einem Verifizierer: Leite anhand des Präfixes (v4.public. gegenüber eyJ) auf getrennte Codepfade zur Prüfung mit getrennten Schlüsseln weiter.
  2. Bilde Claims explizit ab: PASETO reserviert iss, sub, aud, exp, nbf, iat, jti und kid mit Datumsangaben nach ISO 8601; JWT verwendet Sekunden als NumericDate.
  3. Ersetze JWKS durch eine eigene Schlüsselverteilung (PASERK-Strings, Konfigurationsmanagement oder ein Schlüsseldienst), einschließlich Rotation über kid im PASETO-Footer.
  4. Behalte JWT an den Außengrenzen: Viele Systeme stellen JWTs für externe Parteien aus und PASETO intern.
  5. Wenn du bei JWT bleibst, wende RFC 8725 jetzt an: Lege die Algorithmen fest, prüfe typ, iss und aud, verwende 256-Bit-HMAC-Secrets oder asymmetrische Schlüssel, und lehne none ab.

Quellen

Alle Vergleiche