Aller au contenu
ecdsa.com

JWT vs PASETO : standard flexible ou cryptographie figée

PASETO supprime l’en-tête alg et les choix à l’origine de la plupart des vulnérabilités des JWT. JWT conserve l’écosystème : fournisseurs d’identité, standards et bibliothèques dans tous les langages.

Choisir entre JWT et PASETO, c’est arbitrer entre souplesse et résistance aux mauvais usages. JWT (RFC 7519, le plus souvent sous forme de JWS) laisse le jeton indiquer son algorithme, choisi dans un long registre, ce qui a permis des bugs classiques comme alg: none et la confusion entre RS256 et HS256. PASETO (Platform-Agnostic SEcurity TOkens) fixe la cryptographie par version du protocole : un jeton v4 public utilise donc toujours Ed25519, et un jeton v4 local toujours XChaCha20 avec BLAKE2b.

Vue d’ensemble

JWT (JWS)PASETO v4
SpécificationRFC 7519 avec les RFC 7515/7518 (standards IETF)Spécification communautaire (paseto-standard/paseto-spec) ; Internet-Drafts IETF expirés
Formatbase64url(header).base64url(claims).base64url(signature)v4.public. + base64url(message ‖ signature), .footer facultatif
Choix de l’algorithmeEn-tête alg issu du registre IANAFixé par la version et l’usage
Signature à clé publiqueES256, RS256, PS256, EdDSA/Ed25519 et d’autresv4.public : Ed25519 uniquement (v3.public : ECDSA P-384)
Jetons symétriquesHS256 (MAC seul, claims lisibles) ou JWEv4.local : XChaCha20 + BLAKE2b-MAC (chiffré et authentifié)
none et confusion d’algorithmePossibles si le vérificateur est mal configuréImpossibles à exprimer
Assertions implicites (authentifiées, non transmises)NonOui (v3 et v4)
Taille de l’exemple (mesurée plus bas)227 caractères280 caractères
Format de cléJWK et JWKSPASERK (k4.public…, k4.local…)
ÉcosystèmeTous les IdP, OAuth/OIDC, passerelles, services cloudBibliothèques dans de nombreux langages ; peu de prise en charge par les IdP et les passerelles

Différences de conception

Un vérificateur de JWT voit un en-tête qui désigne l’algorithme et doit décider s’il lui fait confiance. La RFC 7518 enregistre plus d’une douzaine d’algorithmes de signature et de MAC, auxquels s’ajoute none, et les bibliothèques choisissaient historiquement leur traitement d’après l’en-tête, ce qui a produit la vague de bugs de 2015 : jetons non signés acceptés, et clés publiques RSA utilisées comme secrets HMAC (voir HS256 vs RS256). La RFC 8725 corrige cela par des règles de configuration que toutes les bibliothèques modernes prennent en charge, mais chaque déploiement doit les appliquer.

PASETO remplace la négociation par des versions. Chaque version fixe une primitive par usage : v4 utilise Ed25519 pour les jetons public, et le chiffrement XChaCha20 avec un MAC BLAKE2b à clé pour les jetons local ; v3 utilise des algorithmes du NIST (ECDSA P-384, AES-256-CTR avec HMAC-SHA-384) pour les environnements qui en ont besoin. Un vérificateur configuré pour les jetons v4 public ne peut pas être amené par la ruse à accepter autre chose, et les clés sont typées, si bien qu’une clé locale ne peut pas servir de clé publique. L’encodage de pré-authentification (PAE) lie la version, l’usage, la charge utile, le pied de page (footer) et d’éventuelles assertions implicites, comme un identifiant de locataire que les deux parties connaissent mais que le jeton ne transporte pas.

Deux autres différences comptent en pratique. Les jetons symétriques local de PASETO sont chiffrés, alors que les jetons symétriques HS256 des JWT sont seulement protégés par un MAC et restent lisibles ; l’équivalent JWT de v4.local est un JWE. Et PASETO n’a pas d’équivalent à la découverte des clés via JWKS : la distribution des clés vous incombe.

Côte à côte

Le script ci-dessous, exécuté avec Node.js 26.10, paseto 3.1.4 et jose 6.2.13, émet les mêmes claims avec une seule clé Ed25519 dans les deux formats.

La même clé Ed25519 et les mêmes claims en PASETO v4.public et en 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);
Sortie
PASETO prefix: v4.public. length: 280 sub: user-42
JWT    prefix: eyJhbGciOi length: 227 sub: user-42

Le jeton PASETO est un peu plus long ici, car la bibliothèque ajoute iat et utilise des horodatages ISO 8601, alors que le JWT utilise des dates numériques ; les signatures ont la même taille. Notez l’option algorithms: ["EdDSA"] côté JWT : c’est précisément cette ligne que PASETO rend inutile.

Écosystème et interopérabilité

JWT est intégré à OAuth 2.0 (jetons d’accès RFC 9068), aux jetons d’ID OpenID Connect, à DPoP, à SD-JWT, à FAPI, à l’identité des charges de travail dans le cloud et à pratiquement toutes les passerelles d’API et tous les fournisseurs d’identité. Des bibliothèques existent pour tous les langages, avec des années de revue de sécurité.

La spécification de PASETO est maintenue sur GitHub par le projet PASETO Standard ; les Internet-Drafts antérieurs de l’IETF ont expiré sans devenir des RFC. Des bibliothèques existent pour de nombreux langages, mais leur qualité et les versions qu’elles couvrent varient, les passerelles et les fournisseurs d’identité acceptent rarement PASETO, et il n’existe aucun mécanisme de découverte normalisé. Choisir PASETO signifie donc que vous gérez vous-même l’émetteur, les vérificateurs et la distribution des clés.

Quand choisir l’un ou l’autre

  • OAuth ou OpenID Connect, consommateurs tiers, passerelles d’API : JWT, avec une liste d’autorisation d’algorithmes par clé.
  • Jetons de service internes dans un système polyglotte que vous contrôlez : l’un ou l’autre ; PASETO si chaque langage dispose d’une bibliothèque v4 maintenue.
  • Jetons chiffrés stockés par les clients (données de session dans un cookie) : PASETO v4.local est plus simple que JWE ; avec JWT, utilisez JWE avec dir ou A256KW et A256GCM.
  • Environnements soumis aux contraintes FIPS : JWT avec ES256 ou PS256, ou PASETO v3 ; XChaCha20 et BLAKE2b, qu’utilise v4, ne sont pas approuvés FIPS.
  • Équipes novices en sécurité des jetons : PASETO élimine toute une classe d’erreurs de configuration ; une bibliothèque JWT aux paramètres par défaut sûrs (comme jose, qui exige que les algorithmes correspondent aux types de clés) réduit l’écart.

Notes de migration

  1. Ne mélangez pas les formats derrière un même vérificateur : aiguillez selon le préfixe (v4.public. ou eyJ) vers des chemins de vérification distincts, avec des clés distinctes.
  2. Faites correspondre les claims explicitement : PASETO réserve iss, sub, aud, exp, nbf, iat, jti et kid, avec des dates ISO 8601 ; JWT utilise des secondes NumericDate.
  3. Remplacez JWKS par votre propre distribution des clés (chaînes PASERK, gestion de configuration ou service de clés), y compris la rotation par kid dans le pied de page PASETO.
  4. Gardez JWT en périphérie : de nombreux systèmes émettent des JWT pour les parties externes et utilisent PASETO en interne.
  5. Si vous restez sur JWT, appliquez la RFC 8725 dès maintenant : imposez les algorithmes, validez typ, iss et aud, utilisez des secrets HMAC de 256 bits ou des clés asymétriques, et rejetez none.

Références

Tous les comparatifs