Aller au contenu
ecdsa.com

HS256 vs RS256 : JWT à secret partagé ou à clé publique

HS256 est un HMAC : tout vérificateur peut aussi émettre des jetons. RS256 est une signature : les vérificateurs ne détiennent qu’une clé publique. La plupart des erreurs de conception autour des JWT viennent de ce que cette différence passe inaperçue.
Éléments comparésHS256RS256

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

HS256RS256
AlgorithmeHMAC 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 bitsPaire de clés RSA, d’au moins 2048 bits
Qui peut vérifierLes détenteurs du secretQuiconque possède la clé publique
Qui peut fabriquer des jetonsChaque détenteur du secret, vérificateurs comprisSeul le détenteur de la clé privée
Distribution des clésCopie confidentielle pour chaque vérificateurJWKS public (jwks_uri)
Partie signature32 octets, 43 caractères base64url256 octets, 342 caractères (RSA-2048)
Jeton d’exemple (mesuré plus bas)187 caractères508 caractères
Vitesse, un cœur d’Apple M4 Maxdes millions de HMAC par secondeenviron 3000 signatures et 114 000 vérifications par seconde
Niveau d’exigence JWAObligatoireRecommandé
Principal mode de défaillanceSecret faible ou divulgué : n’importe qui peut fabriquer des jetonsConfusion 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.

Node.js : jetons HS256 et RS256 pour la même charge utile
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);
Sortie
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 :

  1. Créez la paire de clés dans un KMS ou un HSM et publiez la clé publique dans un JWKS avec un nouveau kid et son alg.
  2. Apprenez aux vérificateurs à gérer les deux : sélectionnez la clé par kid et 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é.
  3. Commencez à émettre des jetons asymétriques avec le nouveau kid ; les jetons HS256 existants restent vérifiables jusqu’à leur expiration.
  4. 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é.
  5. 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

Tous les comparatifs