Le choix entre HS256 et RS256 est la première décision à prendre quand vous émettez des JWT. Les deux valeurs du paramètre alg se ressemblent, mais HS256 authentifie les jetons avec un secret partagé (HMAC-SHA256), tandis que RS256 les signe avec une clé privée RSA (RSASSA-PKCS1-v1_5 avec SHA-256). Cela change qui peut créer des jetons, comment les clés sont distribuées et ce qui se passe quand une clé fuit.
Vue d’ensemble
| HS256 | RS256 | |
|---|---|---|
| Algorithme | HMAC avec SHA-256 (RFC 7518 §3.2) | RSASSA-PKCS1-v1_5 avec SHA-256 (§3.3) |
| Clé | Un seul secret partagé, d’au moins 256 bits | Paire de clés RSA, d’au moins 2048 bits |
| Qui peut vérifier | Les détenteurs du secret | Quiconque possède la clé publique |
| Qui peut fabriquer des jetons | Chaque détenteur du secret, vérificateurs compris | Seul le détenteur de la clé privée |
| Distribution des clés | Copie confidentielle pour chaque vérificateur | JWKS public (jwks_uri) |
| Partie signature | 32 octets, 43 caractères base64url | 256 octets, 342 caractères (RSA-2048) |
| Jeton d’exemple (mesuré plus bas) | 187 caractères | 508 caractères |
| Vitesse, un cœur d’Apple M4 Max | des millions de HMAC par seconde | environ 3000 signatures et 114 000 vérifications par seconde |
| Niveau d’exigence JWA | Obligatoire | Recommandé |
| Principal mode de défaillance | Secret faible ou divulgué : n’importe qui peut fabriquer des jetons | Confusion d’algorithme dans les vérificateurs laxistes ; gestion des clés |
Le modèle de confiance
Avec HS256, la clé de vérification et la clé de signature sont les mêmes octets. Tout service qui vérifie des jetons peut aussi créer des jetons valides pour n’importe quel utilisateur, scope ou audience, et un secret qui fuit depuis l’un d’eux (une variable d’environnement dans un journal, un bundle front-end, une application mobile) permet à n’importe qui de fabriquer des jetons. C’est acceptable pour un monolithe qui émet et vérifie ses propres jetons de session ; c’est une faiblesse sérieuse quand un émetteur de jetons sert de nombreuses API ou équipes.
Avec RS256, l’émetteur conserve la clé privée, idéalement dans un HSM ou un KMS, et publie la clé publique dans un JWKS, à une URL indiquée dans ses métadonnées. Les vérificateurs récupèrent le jeu de clés et sélectionnent les clés par kid, et l’on peut ajouter ou retirer des vérificateurs sans partager de secret. Un vérificateur compromis peut lire les jetons, mais pas les falsifier. C’est pourquoi les fournisseurs OpenID Connect signent par défaut les jetons d’ID avec RS256, et pourquoi la RFC des bonnes pratiques JWT (RFC 8725) oriente les déploiements à plusieurs parties vers les algorithmes asymétriques.
OpenID Connect autorise bien des jetons d’ID HS256 pour les clients confidentiels, avec le secret client comme clé ; cela fonctionne parce que chaque client ne vérifie que les jetons émis pour lui-même.
Robustesse du secret et cassage hors ligne
Un jeton HS256 est un oracle de test hors ligne parfait : quiconque voit un seul jeton peut essayer des secrets candidats aussi vite que son matériel calcule HMAC-SHA256, sans jamais interroger votre serveur. Les secrets courts, choisis par un humain ou laissés par défaut (« secret », un nom de projet, l’exemple d’un tutoriel) sont cassés en quelques secondes avec des outils courants de cassage de mots de passe. La RFC 7518 exige une clé au moins aussi longue que le condensat produit par la fonction de hachage, soit 256 bits pour HS256, et la RFC 8725 §3.5 indique que les clés doivent avoir une entropie suffisante. Générez le secret avec un CSPRNG (openssl rand -base64 32) et ne le dérivez jamais d’un mot de passe.
Avec RS256, il n’existe pas d’attaque hors ligne équivalente contre la clé privée, mais la RFC 7518 n’autorise pas les clés RSA de moins de 2048 bits, et le fichier de clé doit être protégé comme toute clé privée.
La confusion d’algorithme entre les deux
La vulnérabilité JWT la plus connue mélange les deux. Un service vérifie des jetons RS256 en appelant une bibliothèque avec la clé publique RSA au format PEM. Si la bibliothèque prend l’algorithme dans l’en-tête du jeton et prend aussi en charge HS256, un attaquant envoie un jeton avec "alg": "HS256" dont le HMAC a été calculé en prenant comme secret les octets PEM de la clé publique ; la bibliothèque utilise les mêmes octets, et le jeton falsifié passe la vérification. Tim McLean a décrit ce problème en 2015, en même temps que "alg": "none", et la RFC 8725 (§2.1 et §3.1) en codifie le correctif : c’est le vérificateur, et non le jeton, qui décide avec quel algorithme chaque clé peut être utilisée.
Les bibliothèques modernes rendent cela difficile : jsonwebtoken refuse d’utiliser une clé publique comme secret HMAC (l’erreur secretOrPublicKey must be an asymmetric key apparaît quand une configuration confond les deux), PyJWT exige un argument algorithms, et jose associe les algorithmes aux types de clés. Passez tout de même toujours une liste d’autorisation explicite contenant exactement l’algorithme attendu.
Taille et vitesse
Le script ci-dessous, exécuté avec Node.js 26.10, construit les mêmes claims sous forme de jetons HS256 et RS256, avec node:crypto uniquement.
import { createHmac, generateKeyPairSync, randomBytes, sign } from "node:crypto";
const b64url = (value) => Buffer.from(value).toString("base64url");
const payload = b64url(JSON.stringify({ iss: "https://auth.example.com", sub: "user-42", aud: "api", exp: 1792500000 }));
// HS256: HMAC-SHA256 with a shared 256-bit secret.
const secret = randomBytes(32);
const hsInput = `${b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }))}.${payload}`;
const hs256 = `${hsInput}.${b64url(createHmac("sha256", secret).update(hsInput).digest())}`;
// RS256: RSASSA-PKCS1-v1_5 with SHA-256 and a 2048-bit key.
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const rsInput = `${b64url(JSON.stringify({ alg: "RS256", typ: "JWT", kid: "2026-10" }))}.${payload}`;
const rs256 = `${rsInput}.${b64url(sign("sha256", Buffer.from(rsInput), privateKey))}`;
console.log("HS256 token:", hs256.length, "characters, signature part:", hs256.split(".")[2].length);
console.log("RS256 token:", rs256.length, "characters, signature part:", rs256.split(".")[2].length);
HS256 token: 187 characters, signature part: 43
RS256 token: 508 characters, signature part: 342
L’en-tête RS256 porte aussi un kid, dont les vérificateurs ont besoin pour choisir la clé dans un JWKS. En production, utilisez une bibliothèque JWT maintenue plutôt que des jetons construits à la main ; ce qui compte ici, c’est la taille de la partie signature. Pour le même compromis côté ECDSA, consultez ES256 vs RS256.
Quand choisir l’un ou l’autre
- Une seule application qui émet et vérifie ses propres jetons de session : HS256 avec un secret aléatoire de 32 octets, renouvelé par rotation avec une courte période de chevauchement.
- Un serveur d’autorisation dont les jetons sont vérifiés par plusieurs API : RS256, PS256 ou ES256, avec des clés publiées dans un JWKS.
- Jetons vérifiés par des tiers, des applications mobiles ou des navigateurs : asymétrique uniquement ; un secret livré à un client est public.
- Jetons d’ID OpenID Connect pour des clients confidentiels : les deux conviennent ; l’asymétrique est le choix par défaut et il est plus simple à exploiter.
- Webhooks vers des clients individuels : les schémas de type HMAC sont courants, car chaque client a son propre secret.
- Garde des clés imposée par la conformité (HSM, KMS) : clés asymétriques, puisque les services peuvent vérifier sans accéder au matériel de signature.
Notes de migration
Passer de HS256 à RS256 ou à ES256 sans tout basculer le même jour :
- Créez la paire de clés dans un KMS ou un HSM et publiez la clé publique dans un JWKS avec un nouveau
kidet sonalg. - Apprenez aux vérificateurs à gérer les deux : sélectionnez la clé par
kidet liez chaque clé à un seul algorithme (le secret partagé à HS256, la nouvelle clé à RS256). Ne laissez jamais un même appel de vérification accepter les deux algorithmes pour le même matériel de clé. - Commencez à émettre des jetons asymétriques avec le nouveau
kid; les jetons HS256 existants restent vérifiables jusqu’à leur expiration. - Retirez le secret partagé de chaque vérificateur une fois écoulée la plus longue durée de vie des jetons HS256. C’est cette étape qui apporte le véritable gain de sécurité.
- Renouvelez tout de même l’ancien secret s’il existe le moindre risque qu’il ait fuité, car un attaquant qui le détient pourrait encore fabriquer des jetons HS256 jusqu’à ce que les vérificateurs cessent de les accepter.
Références
- RFC 7518, JSON Web Algorithms (§3.2 HMAC, §3.3 RSASSA-PKCS1-v1_5)
- RFC 8725, bonnes pratiques actuelles pour les JWT (§2.1, §3.1, §3.5)
- RFC 7519, JSON Web Token
- RFC 7517, JSON Web Key et JWK Set
- OpenID Connect Core 1.0 §10.1, signature avec des algorithmes MAC
- Auth0 : vulnérabilités critiques dans les bibliothèques JSON Web Token (2015)