Chapitre 617 minIntermédiaire
Défaillances cryptographiques (A04) : hacher, chiffrer, HTTPS
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- distinguer hachage, chiffrement et encodage ;
- stocker un mot de passe avec un algorithme lent et salé ;
- chiffrer une donnée qu'on doit relire sans exposer la clé ;
- expliquer ce que TLS protège ;
- générer un jeton imprévisible.
4 exercices corrigés1 schéma
Les défaillances cryptographiques (Cryptographic Failures, A04:2025) regroupent les données sensibles mal protégées : transmises sans chiffrement, stockées en clair ou avec un algorithme trop faible, protégées par des clés mal gardées ou un aléatoire prévisible. Ce chapitre explique la différence entre hachage et chiffrement, comment stocker un mot de passe, comment chiffrer une donnée qu'on doit relire, à quoi sert TLS et quelles erreurs éviter.
Identifier les données à protéger
Avant de choisir un algorithme, on classe les données. Dans AppliFrais :
| Donnée | Sensibilité | Ce qu'il faut |
|---|---|---|
| Mot de passe | Critique : il ouvre le compte, et souvent d'autres comptes de la personne | Ne jamais le stocker, même chiffré : on stocke une empreinte lente et salée |
| RIB du visiteur | Élevée : donnée financière | Chiffrée en base, déchiffrée seulement pour la mise en paiement |
| Montants, déplacements | Moyenne : données personnelles | Accès contrôlé, chiffrement du disque et des sauvegardes |
| Barème kilométrique | Publique | Aucune confidentialité ; seulement l'intégrité |
Les données sont à protéger à deux moments : en transit, quand elles circulent sur le réseau (TLS), et au repos, quand elles sont stockées en base, sur disque ou dans une sauvegarde (hachage, chiffrement).
Hachage ou chiffrement : la différence
| Hachage | Chiffrement | |
|---|---|---|
| Sens | À sens unique : impossible de retrouver la donnée d'origine | Réversible : avec la clé, on retrouve la donnée |
| Clé | Aucune | Une clé (symétrique) ou une paire de clés (asymétrique) |
| Taille du résultat | Fixe, quelle que soit l'entrée | Proche de la taille de l'entrée |
| Usage | Vérifier un mot de passe, contrôler l'intégrité d'un fichier | Protéger une donnée qu'on doit relire : RIB, dossier, message |
| Exemples | Argon2id, bcrypt, scrypt (mots de passe) ; SHA-256, SHA-3 (intégrité) | AES-256-GCM, ChaCha20 (symétrique) ; RSA, courbes elliptiques (asymétrique) |
Une fonction de hachage transforme une entrée de taille quelconque en une empreinte de taille fixe. Elle est déterministe (la même entrée donne toujours la même empreinte), à sens unique, et la moindre modification de l'entrée change toute l'empreinte :
SHA-256("azerty") = f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9
SHA-256("azertz") = 7dd6e0b180a8a39b2480194fd498092b474415346da395c3cd12b9ca1b8a5a8c
Stocker un mot de passe
On ne stocke jamais un mot de passe, ni en clair, ni chiffré : avec la clé, un attaquant ou un administrateur indélicat déchiffrerait tout. On stocke son empreinte. À la connexion, on calcule l'empreinte du mot de passe saisi et on la compare à celle de la base.
Mais une simple empreinte SHA-256 ne suffit pas, pour deux raisons.
Les attaques par dictionnaire et par tables précalculées. Les mêmes mots de passe donnent les mêmes empreintes. Un attaquant qui vole la base compare les empreintes à des listes d'empreintes de mots de passe courants (rainbow tables) : f2d81a26… est reconnu immédiatement comme azerty. La parade est le sel : une valeur aléatoire, différente pour chaque compte, ajoutée au mot de passe avant le hachage et stockée à côté de l'empreinte. Deux utilisateurs qui ont choisi azerty ont alors des empreintes différentes, et les tables précalculées ne servent plus à rien.
La vitesse. SHA-256 et MD5 sont conçus pour être rapides. Une seule carte graphique calcule des milliards d'empreintes MD5 par seconde : essayer toutes les combinaisons d'un mot de passe court devient trivial. Pour les mots de passe, on veut au contraire une fonction volontairement lente et gourmande en mémoire, dont on règle le coût.
Les ordres de grandeur suivants montrent l'effet de la lenteur. Ils supposent un attaquant qui teste toutes les combinaisons d'un mot de passe de 8 caractères parmi les 62 lettres et chiffres, soit 62⁸ ≈ 2,18 × 10¹⁴ possibilités :
| Algorithme | Vitesse d'essai (ordre de grandeur, une carte graphique) | Temps pour tout essayer |
|---|---|---|
| MD5 | 10¹⁰ empreintes par seconde | environ 6 heures |
| bcrypt, coût 12 | 10⁴ empreintes par seconde | environ 690 ans |
Les vitesses réelles dépendent du matériel ; l'écart, lui, reste de plusieurs ordres de grandeur.
Les algorithmes recommandés par l'OWASP pour les mots de passe :
| Algorithme | Recommandation |
|---|---|
| Argon2id | Premier choix : au minimum 19 Mio de mémoire, 2 itérations, 1 degré de parallélisme |
| scrypt | Si Argon2id n'est pas disponible |
| bcrypt | Pour les systèmes existants : facteur de coût d'au moins 10 ; limité à 72 octets de mot de passe |
| PBKDF2-HMAC-SHA256 | Quand une conformité FIPS l'impose : 600 000 itérations |
| MD5, SHA-1, SHA-256 seuls | Jamais pour un mot de passe |
Ces fonctions génèrent le sel elles-mêmes et l'intègrent à l'empreinte, avec l'algorithme et le coût. Tu n'as rien à gérer à la main. En PHP :
// Inscription : PASSWORD_DEFAULT utilise bcrypt, avec un coût de 12 depuis PHP 8.4
$hash = password_hash($motDePasse, PASSWORD_DEFAULT);
// ou, si PHP a été compilé avec Argon2 :
$hash = password_hash($motDePasse, PASSWORD_ARGON2ID);
// $hash ressemble à : $2y$12$… (algorithme, coût, sel et empreinte dans une seule chaîne)
// Connexion
if (password_verify($saisie, $hashEnBase)) {
if (password_needs_rehash($hashEnBase, PASSWORD_DEFAULT)) {
// l'algorithme ou le coût par défaut a changé : on met l'empreinte à jour
$nouveau = password_hash($saisie, PASSWORD_DEFAULT);
// UPDATE utilisateur SET mdp_hash = ? WHERE id = ?
}
// connexion réussie
}
En Node.js, avec le paquet argon2 :
import argon2 from 'argon2';
const hash = await argon2.hash(motDePasse); // Argon2id par défaut, sel aléatoire inclus
const valide = await argon2.verify(hashEnBase, saisie); // true ou false
La colonne mdp_hash doit être assez longue : 255 caractères (VARCHAR(255)) laissent de la marge pour un changement d'algorithme.
Chiffrer une donnée qu'on doit relire
Le RIB doit être relu pour payer : on le chiffre. Le chiffrement symétrique utilise la même clé pour chiffrer et déchiffrer ; c'est le bon choix pour des données stockées. L'algorithme de référence est AES, avec une clé de 256 bits et le mode GCM, qui chiffre et garantit en plus l'intégrité : toute modification du texte chiffré est détectée au déchiffrement.
import crypto from 'node:crypto';
// Clé de 32 octets (256 bits), lue depuis un secret, jamais écrite dans le code
const cle = Buffer.from(process.env.CLE_RIB, 'hex');
export function chiffrer(texte) {
const iv = crypto.randomBytes(12); // vecteur d'initialisation, unique à chaque fois
const chiffreur = crypto.createCipheriv('aes-256-gcm', cle, iv);
const chiffre = Buffer.concat([chiffreur.update(texte, 'utf8'), chiffreur.final()]);
const tag = chiffreur.getAuthTag(); // 16 octets qui garantissent l'intégrité
return Buffer.concat([iv, tag, chiffre]).toString('base64');
}
export function dechiffrer(donnee) {
const brut = Buffer.from(donnee, 'base64');
const iv = brut.subarray(0, 12), tag = brut.subarray(12, 28), chiffre = brut.subarray(28);
const dechiffreur = crypto.createDecipheriv('aes-256-gcm', cle, iv);
dechiffreur.setAuthTag(tag); // lève une erreur si la donnée a été modifiée
return Buffer.concat([dechiffreur.update(chiffre), dechiffreur.final()]).toString('utf8');
}
Trois règles :
- Le vecteur d'initialisation (IV) est unique pour chaque chiffrement avec la même clé. Il n'est pas secret : on le stocke avec la donnée chiffrée. Réutiliser un IV avec AES-GCM détruit la sécurité.
- La clé ne se stocke pas à côté des données. Une clé dans la même base que les RIB chiffrés ne protège de rien si la base est volée. Elle va dans un coffre-fort de secrets ou un service de gestion de clés (KMS), et l'accès y est limité.
- On n'invente pas sa cryptographie. On utilise les fonctions éprouvées du langage (
cryptode Node.js,sodium_crypto_secretboxouopenssl_encrypten PHP) et des modes reconnus.
Les SGBD proposent aussi du chiffrement : chiffrement de colonnes, chiffrement transparent des fichiers de la base (TDE), chiffrement des sauvegardes. Le chapitre Injection SQL et protection des données du cours SQL montre AES_ENCRYPT sous MySQL et le chiffrement sous SQL Server, sujet d'annales de l'E7.
Chiffrement asymétrique et signature
Le chiffrement asymétrique utilise une paire de clés : une clé publique, que tout le monde peut connaître, et une clé privée, que seul son propriétaire détient. Ce qui est chiffré avec la clé publique ne se déchiffre qu'avec la clé privée. Il est lent, et sert surtout à deux choses :
- établir une clé symétrique commune au début d'une connexion TLS (échange Diffie-Hellman, la clé privée du certificat servant à le signer pour prouver l'identité du serveur) ;
- signer : le propriétaire calcule une signature avec sa clé privée ; tout le monde la vérifie avec la clé publique. La signature prouve l'origine et l'intégrité d'un document, d'une mise à jour ou d'un jeton JWT signé en RS256 ou ES256.
Les algorithmes courants : RSA (clés de 3 072 bits ou plus pour un usage durable), et les courbes elliptiques (ECDSA, Ed25519), plus rapides à sécurité égale. Les certificats, les autorités de certification et la PKI sont traités dans le cours Cybersécurité des infrastructures.
HTTPS et TLS : protéger les échanges
HTTPS est HTTP transporté dans une connexion chiffrée par TLS. TLS garantit trois choses : la confidentialité (personne sur le réseau ne lit le mot de passe ou le jeton), l'intégrité (personne ne modifie la page en route) et l'authentification du serveur (le certificat prouve que tu parles bien à frais.gsb.fr, et pas à un imposteur sur le Wi-Fi public).
Les règles actuelles :
- HTTPS partout, pas seulement sur la page de connexion : un cookie de session envoyé une seule fois en HTTP peut être volé.
- TLS 1.2 au minimum, TLS 1.3 de préférence. TLS 1.0 et 1.1 sont officiellement obsolètes depuis 2021 (RFC 8996), SSL depuis bien plus longtemps.
- Un certificat valide, délivré par une autorité reconnue. Let's Encrypt en délivre gratuitement, renouvelés automatiquement. La durée de validité maximale des certificats diminue progressivement : 200 jours depuis mars 2026, et 47 jours prévus en 2029. Le renouvellement doit donc être automatisé.
- Redirection de HTTP vers HTTPS et en-tête HSTS, qui interdit au navigateur de revenir en HTTP (chapitre 9).
- Les connexions internes aussi : entre l'API et la base, entre l'application et un service de mail, quand elles traversent un réseau partagé.
La mise en place d'un certificat sur un hébergement est expliquée dans le cours HTML et CSS.
Générer de l'aléatoire sûr
Les jetons de session, les jetons de réinitialisation de mot de passe, les sels, les IV et les clés doivent être imprévisibles. Les générateurs ordinaires ne le sont pas : Math.random() en JavaScript, rand() et mt_rand() en PHP, java.util.Random produisent des suites qu'un attaquant peut prédire à partir de quelques valeurs.
| Langage | À éviter pour la sécurité | À utiliser |
|---|---|---|
| JavaScript (Node.js) | Math.random() | crypto.randomBytes(32), crypto.randomUUID(), crypto.randomInt() |
| PHP | rand(), mt_rand(), uniqid() | random_bytes(32), random_int() |
| Java | java.util.Random | java.security.SecureRandom |
| C# | System.Random | RandomNumberGenerator |
$jeton = bin2hex(random_bytes(32)); // 64 caractères hexadécimaux, 256 bits d'aléa
Les erreurs cryptographiques classiques
| Erreur | Pourquoi c'est grave | La bonne pratique |
|---|---|---|
| Mots de passe en clair ou chiffrés | Une fuite les révèle tous | Argon2id ou bcrypt |
| MD5, SHA-1 ou SHA-256 sans sel pour les mots de passe | Retrouvés en masse par tables ou force brute | Fonction lente et salée |
| Clé écrite dans le code ou stockée avec les données | Le chiffrement ne protège plus rien | Coffre-fort de secrets, KMS |
| IV fixe ou réutilisé | Révèle des informations sur les données | IV aléatoire à chaque chiffrement |
| Mode ECB | Deux blocs identiques donnent le même chiffré : les motifs restent visibles | AES-GCM |
| Aléatoire prévisible pour un jeton | Jetons devinables | Générateur cryptographique |
| Pages sensibles en HTTP, TLS 1.0 | Interception des données et des cookies | HTTPS partout, TLS 1.2 ou 1.3, HSTS |
| Algorithme « maison » | Personne ne l'a analysé : il a des failles | Bibliothèques éprouvées |
Ce chapitre résume ce que la sécurité applicative attend de la cryptographie. Pour le fonctionnement interne des algorithmes et des certificats, appuie-toi sur le cours Cybersécurité des infrastructures.
Exercice 1 sur 4 Hacher ou chiffrer ? Réussi À revoir
Pour chaque donnée d'AppliFrais, choisis : hachage lent et salé, chiffrement symétrique, empreinte SHA-256, ou rien.
- Le mot de passe d'un visiteur.
- L'IBAN d'un visiteur, utilisé chaque mois pour le virement.
- Un justificatif PDF dont on veut prouver qu'il n'a pas été modifié depuis son envoi.
- Le libellé d'un frais (« Hôtel Ibis Lyon »).
- Le jeton de réinitialisation de mot de passe, stocké en base pendant 30 minutes.
Voir la réponseMasquer la réponse de l'exercice 1
- Hachage lent et salé (Argon2id ou bcrypt) : on n'a jamais besoin de le relire, seulement de le vérifier.
- Chiffrement symétrique (AES-256-GCM) : il faut le relire pour payer ; la clé est stockée hors de la base.
- Empreinte SHA-256 du fichier, calculée à l'envoi et stockée ; on la recalcule pour vérifier l'intégrité. Pour prouver aussi qui a produit le document, on utiliserait une signature.
- Rien de particulier : c'est une donnée personnelle ordinaire, protégée par le contrôle d'accès et le chiffrement du disque et des sauvegardes.
- Empreinte du jeton (SHA-256 suffit ici, car le jeton est long et aléatoire, donc impossible à deviner par force brute) : si la base fuite, les jetons en cours ne sont pas utilisables. Le jeton en clair n'existe que dans le lien envoyé par mail.
Tu avais trouvé ?
Exercice 2 sur 4 Ordres de grandeur Réussi À revoir
Un attaquant a volé une base où les mots de passe sont hachés en MD5 sans sel. Il teste 10¹⁰ empreintes par seconde.
- Combien de temps lui faut-il pour essayer tous les codes PIN à 6 chiffres ?
- Et tous les mots de passe de 8 lettres minuscules (26 lettres) ?
- Le même mot de passe de 8 minuscules est maintenant haché avec bcrypt ; l'attaquant ne teste plus que 10⁴ empreintes par seconde. Combien de temps lui faut-il ?
- Que montrent ces résultats sur le choix de l'algorithme et sur la longueur des mots de passe ?
Voir la réponseMasquer la réponse de l'exercice 2
- 10⁶ combinaisons ÷ 10¹⁰ par seconde = 10⁻⁴ seconde : instantané.
- 26⁸ ≈ 2,09 × 10¹¹ combinaisons ÷ 10¹⁰ = environ 21 secondes.
- 2,09 × 10¹¹ ÷ 10⁴ ≈ 2,09 × 10⁷ secondes, soit environ 242 jours.
- L'algorithme lent multiplie le temps d'attaque par un million : le même mot de passe passe de 21 secondes à huit mois. Mais un mot de passe court et pauvre reste à la portée d'un attaquant patient : il faut les deux, une fonction lente et des mots de passe longs (politique de mots de passe).
Tu avais trouvé ?
Exercice 3 sur 4 Corriger un stockage de mots de passe Réussi À revoir
Une application Node.js enregistre les comptes ainsi :
const hash = crypto.createHash('md5').update(req.body.mdp).digest('hex');
await db.execute('INSERT INTO utilisateur (login, mdp_hash) VALUES (?, ?)', [req.body.login, hash]);
- Cite deux défauts de ce stockage.
- Réécris ces deux lignes avec Argon2id.
- La base contient déjà 300 comptes en MD5. Comment migrer sans demander à tout le monde de changer de mot de passe ?
Voir la réponseMasquer la réponse de l'exercice 3
- MD5 est rapide (des milliards d'essais par seconde) et utilisé sans sel : deux mots de passe identiques ont la même empreinte, retrouvable dans des tables précalculées. MD5 est par ailleurs cassé pour les usages cryptographiques.
- Avec le paquet
argon2:
const hash = await argon2.hash(req.body.mdp);
await db.execute('INSERT INTO utilisateur (login, mdp_hash) VALUES (?, ?)', [req.body.login, hash]);
- À la prochaine connexion réussie de chaque utilisateur, on vérifie le mot de passe saisi contre l'ancienne empreinte MD5, puis on calcule et on enregistre son empreinte Argon2id. Les comptes qui ne se reconnectent pas pendant une période fixée sont désactivés et devront réinitialiser leur mot de passe. Une variante consiste à hacher tout de suite les empreintes MD5 existantes avec Argon2id (Argon2id(MD5(mdp))) pour ne plus laisser de MD5 nu en base.
Tu avais trouvé ?
Exercice 4 sur 4 Repérer l'erreur Réussi À revoir
Pour chaque extrait, nomme l'erreur cryptographique.
const jeton = Math.random().toString(36).slice(2);pour un lien de réinitialisation.const iv = Buffer.alloc(12, 0);avant chaque appel àcreateCipheriv('aes-256-gcm', …).- La clé AES est stockée dans une table
parametrede la même base que les IBAN chiffrés. - Le formulaire de connexion est servi en HTTPS, mais toutes les autres pages en HTTP.
Voir la réponseMasquer la réponse de l'exercice 4
- Aléatoire prévisible :
Math.random()n'est pas un générateur cryptographique. Utilisercrypto.randomBytes(32).toString('hex'). - IV fixe, réutilisé à chaque chiffrement : avec GCM, c'est une faille grave. Tirer un IV avec
crypto.randomBytes(12)à chaque fois et le stocker avec le chiffré. - Clé stockée avec les données : qui vole la base vole aussi la clé. La placer dans un coffre-fort de secrets ou une variable d'environnement du serveur, avec un accès restreint.
- HTTPS partiel : le cookie de session circule en clair sur les autres pages et peut être intercepté. HTTPS sur tout le site, cookie
Secureet en-tête HSTS.
Tu avais trouvé ?
Pour aller plus loin. Le cours Cryptographie reprend chaque outil en profondeur : chiffrement symétrique, fonction de hachage, stockage des mots de passe et TLS 1.3.