Skip to Content
SécuritéPartie 3 — Cryptographie pour devs3.4 — Signatures, JWT & aléatoire

Chapitre 3.4 — Signatures, JWT & aléatoire

⏱️ TL;DR — Une signature prouve deux choses sur une donnée : son intégrité (non modifiée) et son origine (qui l’a émise). En symétrique, c’est une HMAC (secret partagé) ; en asymétrique, une signature avec clé privée, vérifiée avec la publique. Un JWT est un jeton signé (pas chiffré : son contenu est juste encodé en Base64, donc lisible — n’y mets pas de secret). Ses pièges sont célèbres : accepter alg: none, confondre les clés, oublier de vérifier l’expiration. Enfin, tout secret (token, id de session, sel, code de reset) doit venir d’un CSPRNGcrypto.randomBytes / random_bytes()jamais de Math.random(), qui est prévisible.

🎯 Objectifs

  • Comprendre ce que garantit une signature (intégrité + origine) et la différence HMAC vs signature asymétrique.
  • Savoir ce qu’est un JWT, ce qu’il protège (intégrité) et ce qu’il **n’**assure pas (confidentialité).
  • Éviter les pièges JWT : alg: none, confusion de clé, expiration, secrets dans le payload, révocation.
  • Générer de l’aléatoire cryptographiquement sûr avec un CSPRNG, jamais Math.random()/rand().

Signer : intégrité + origine

Chiffrer protège la confidentialité. Signer protège l’intégrité et l’authenticité : le destinataire peut vérifier que la donnée n’a pas été modifiée et qu’elle vient bien de l’émetteur attendu. Une signature ne cache pas la donnée — elle la certifie.

Deux mécanismes :

  • HMAC (symétrique) : émetteur et vérifieur partagent un secret. L’émetteur calcule HMAC(secret, données) et l’attache ; le vérifieur recalcule et compare. Simple et rapide, mais suppose un secret partagé (donc pas de distinction « qui parmi ceux qui ont le secret »).
  • Signature asymétrique : l’émetteur signe avec sa clé privée ; tout le monde vérifie avec sa clé publique. Avantage : le vérifieur n’a pas besoin du secret, et la signature identifie un émetteur précis (celui qui détient la privée). C’est ce qui certifie les certificats TLS, les paquets logiciels, la provenance (voir Partie 12).

💡 Réflexe — « Signer » ≠ « chiffrer ». Si tu veux qu’une donnée soit infalsifiable mais pas secrète (un jeton lisible mais non modifiable, un webhook authentifié), tu signes (HMAC/signature). Si tu veux qu’elle soit secrète, tu chiffres. Les deux sont parfois combinés, mais ce sont des besoins distincts — nomme le tien avant de choisir.

Le JWT : un jeton signé (et lisible)

Un JWT (JSON Web Token) est un jeton compact en trois parties séparées par des points : header.payload.signature. Le header et le payload sont du JSON encodé en Base64URL ; la signature (HMAC en HS256, ou asymétrique en RS256/ES256) couvre les deux premières parties.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.d3aB... ^ header (Base64) ^ payload (Base64, LISIBLE) ^ signature

Le point le plus mal compris : le payload n’est pas chiffré. N’importe qui peut le décoder (c’est du Base64, voir chapitre 3.1). La signature empêche de le modifier sans invalider le jeton, mais elle ne le cache pas.

⚠️ PiègeNe mets jamais de secret dans un JWT (mot de passe, clé, données personnelles sensibles). Le payload est lisible par le porteur et par quiconque intercepte le jeton. Un JWT garantit l’intégrité (« ces claims n’ont pas été modifiés »), pas la confidentialité. Si tu as besoin de secret, chiffre — ou, mieux, garde la donnée côté serveur et ne mets dans le jeton qu’un identifiant.

Les pièges classiques du JWT

PiègeLe dangerLa parade
alg: none acceptéL’attaquant envoie un jeton avec alg:none (aucune signature) et le serveur l’accepte → jeton forgé.Fixer l’algorithme attendu côté serveur ; refuser none.
Confusion de clé (RS256 → HS256)Le serveur attend du RS256 mais accepte du HS256 signé avec la clé publique (connue) comme secret HMAC → forge.Fixer l’algorithme et le type de clé attendus.
Expiration ignoréeUn jeton volé reste valable indéfiniment.Vérifier exp ; durées courtes ; refresh tokens.
Pas de révocationUn JWT auto-porté ne peut pas être « déconnecté » avant son expiration.Durées courtes + liste de révocation, ou session serveur classique.
Secret HMAC faibleSecret devinable → forge de jetons.Secret long et aléatoire (CSPRNG), dans le vault.

La leçon : utilise une bibliothèque JWT à jour et configure-la strictement (algorithme fixé, vérification de exp, iss/aud si pertinent). Ne parse jamais un JWT « à la main ».

🎯 Côté attaquant — Devant un JWT, l’attaquant commence par le décoder (le payload est en clair) pour voir les claims — souvent un role, un userId. Puis il tente de le modifier : passer role à admin et re-signer. Ça échoue si la signature est bien vérifiée… sauf s’il peut exploiter alg:none, une confusion de clé, ou un secret HMAC faible. D’où : algorithme fixé, signature toujours vérifiée, secret fort. Et comme le payload est lisible, il y cherche aussi des infos sensibles qu’un dev y aurait mises par erreur.

L’aléatoire : le CSPRNG, jamais Math.random()

Une immense quantité de sécurité repose sur des valeurs imprévisibles : identifiants de session, tokens de reset de mot de passe, sels, clés, codes à usage unique, state OAuth. Si un attaquant peut prédire ces valeurs, il contourne la protection (deviner un token de reset = prendre le compte).

Or les générateurs « ordinaires » ne sont pas imprévisibles :

// ❌ Math.random() : NON cryptographique, previsible. const token = Math.random().toString(36).slice(2) // devinable, JAMAIS pour la securite // ✅ CSPRNG : imprevisible import { randomBytes, randomUUID } from 'crypto' const token = randomBytes(32).toString('hex') // 256 bits d'aleatoire sur const id = randomUUID() // UUID v4 sur (Node/navigateur : crypto.randomUUID)
// ✅ PHP : random_bytes / random_int sont des CSPRNG $token = bin2hex(random_bytes(32)); // 256 bits, sur $code = random_int(100000, 999999); // OTP a 6 chiffres, imprevisible // ❌ rand() / mt_rand() : NON cryptographiques

Règle : pour tout ce qui touche à la sécurité, utilise un CSPRNG (Cryptographically Secure PRNG) — crypto.randomBytes/crypto.getRandomValues/crypto.randomUUID (JS), random_bytes()/random_int() (PHP), le générateur sécurisé de ta plateforme. Jamais Math.random(), rand(), mt_rand(), ni un timestamp, ni un compteur.

⚠️ PiègeMath.random() est prévisible : il n’est pas conçu pour la sécurité et son état peut être reconstruit à partir de quelques sorties. Un token de reset généré avec Math.random(), un id de session basé sur l’heure, un « code secret » incrémental : autant de valeurs devinables qu’un attaquant peut énumérer. Le réflexe : dès que la valeur doit être imprévisible, c’est un CSPRNG, point.

🧭 Sur FormaCampus — FormaCampus signe ses jetons inter-services en RS256 (asymétrique), avec l’algorithme fixé côté vérifieur (jamais alg:none, jamais de confusion de clé), une expiration courte et une vérification systématique de exp/iss/aud via une bibliothèque à jour. Aucune donnée sensible n’est mise dans les payloads (juste un userId). Tous les secrets imprévisibles — id de session, tokens de reset, state OAuth, codes OTP — sont générés par un CSPRNG (random_bytes côté Symfony, crypto.randomBytes côté Node). Math.random() est proscrit dans tout code de sécurité.

✏️ Exercices

Exercice 1 — Signer ou chiffrer ? Tu envoies un webhook à un partenaire ; il doit pouvoir vérifier que le message vient bien de toi et n’a pas été altéré, mais le contenu n’est pas secret. Que fais-tu, et avec quel mécanisme si vous partagez déjà un secret ?

✅ Solution

Tu signes (pas besoin de chiffrer, le contenu n’est pas secret). Comme vous partagez déjà un secret, une HMAC convient : tu calcules HMAC(secret, corps) et tu l’envoies dans un en-tête (ex. X-Signature) ; le partenaire recalcule et compare (en temps constant). Cela prouve l’intégrité (corps non modifié) et l’origine (seul le détenteur du secret a pu signer). C’est le schéma qu’utilisent la plupart des webhooks.

Exercice 2 — Le token devinable. Un dev génère les tokens de réinitialisation de mot de passe ainsi : token = Date.now().toString(36) + userId. Explique la faille et corrige.

✅ Solution

Le token est entièrement prévisible : Date.now() est estimable (l’attaquant connaît à peu près l’heure de la demande) et userId est souvent connu ou énumérable. Un attaquant peut donc reconstituer le token de reset d’une victime et prendre son compte. Il faut un token imprévisible issu d’un CSPRNG, non lié à des valeurs devinables : par ex. crypto.randomBytes(32).toString('hex'), stocké haché côté serveur, à usage unique et à expiration courte. L’imprévisibilité est la sécurité ici.

🧠 Quiz de révision

1. Que garantit une signature, et que ne garantit-elle pas ?

Elle garantit l’intégrité (donnée non modifiée) et l’origine/authenticité (émetteur attendu). Elle ne garantit pas la confidentialité : une donnée signée reste lisible. Pour la cacher, il faut chiffrer.

2. Le payload d’un JWT est-il chiffré ?

Non : il est simplement encodé en Base64URL, donc lisible par quiconque. La signature empêche de le modifier, pas de le lire. On n’y met donc jamais de secret ni de donnée sensible.

3. Cite deux pièges classiques des JWT et leur parade.

Par ex. : accepter alg: none (jeton sans signature accepté) → fixer l’algorithme attendu ; ignorer l’expiration (exp) → un jeton volé reste valable indéfiniment → vérifier exp + durées courtes. Aussi : confusion de clé RS256/HS256, secret HMAC faible, absence de révocation.

4. Pourquoi Math.random() est-il interdit pour un token de sécurité ?

Parce qu’il est non cryptographique et prévisible : son état peut être reconstruit, ses sorties devinées. Un token, id de session ou code généré ainsi est énumérable par un attaquant. On utilise un CSPRNG (crypto.randomBytes, random_bytes()).

5. HMAC vs signature asymétrique : quelle différence pratique ?

HMAC : secret partagé entre émetteur et vérifieur (rapide, mais tous ceux qui ont le secret peuvent signer). Signature asymétrique : l’émetteur signe avec sa clé privée, tous vérifient avec la publique — le vérifieur n’a pas besoin du secret et la signature identifie un émetteur précis.


Fin de la Partie 3. Tu sais désormais encoder/hacher/chiffrer à bon escient, stocker un mot de passe, signer et générer de l’aléatoire sûr. Direction la Partie 4 — Authentification, où l’on assemble tout ça pour prouver « qui tu es » de façon robuste.

Last updated on