Chapitre 3.3 — Hacher les mots de passe
⏱️ TL;DR — Un mot de passe ne se stocke jamais en clair, jamais chiffré (réversible = danger), et jamais avec un simple SHA/MD5 (trop rapide → cassable en masse). On le hache avec un algorithme conçu pour ça : lent, salé (un sel unique par utilisateur), et paramétrable en coût. Le choix recommandé aujourd’hui est argon2id ; bcrypt et scrypt restent solides. Concrètement, tu utilises l’API de ta plateforme —
password_hash()/password_verify()en PHP,argon2/bcrypten Node, lePasswordHasherde Symfony — qui gèrent sel et coût pour toi. Et tu compares en temps constant. Simple à faire, catastrophique à rater.
🎯 Objectifs
- Comprendre pourquoi le clair, le chiffrement et un SHA nu sont tous inadaptés aux mots de passe.
- Savoir ce qu’apportent le sel, la lenteur et le coût paramétrable.
- Choisir un algorithme adapté : argon2id (recommandé), bcrypt, scrypt.
- Utiliser les API standard (
password_hash, SymfonyPasswordHasher, argon2 en Node) correctement. - Vérifier un mot de passe (comparaison en temps constant) et gérer le rehash.
Pourquoi les mauvaises méthodes sont mauvaises
| Méthode | Problème |
|---|---|
| En clair | Une fuite de base = tous les mots de passe exposés, réutilisables ailleurs (les gens réutilisent). Impardonnable. |
| Chiffré (réversible) | Si la base et la clé fuient (souvent ensemble), tout redevient clair. On ne doit jamais pouvoir retrouver un mot de passe. |
| SHA-256 / MD5 nu | Trop rapide : un GPU teste des milliards de hashes/seconde → bruteforce massif. Sans sel, les mots de passe identiques ont le même hash et tombent via rainbow tables. |
Le cœur du problème avec un hash « classique » : il est conçu pour être rapide (vérifier l’intégrité d’un gros fichier vite). Or pour un mot de passe, la rapidité est l’ennemi — elle profite à l’attaquant qui teste des milliards de candidats. Il faut exactement l’inverse : un hachage délibérément lent.
Les trois ingrédients d’un bon hachage de mot de passe
1. Le sel (salt)
Un sel est une valeur aléatoire, unique par utilisateur, ajoutée au mot de passe avant hachage. Effet :
- Deux utilisateurs avec le même mot de passe obtiennent des hashes différents → on ne voit plus les doublons.
- Les rainbow tables (tables de hashes précalculés) deviennent inutiles : il faudrait une table par sel.
Bonne nouvelle : les algorithmes modernes génèrent et stockent le sel automatiquement dans la chaîne de sortie. Tu n’as pas à le gérer à la main.
2. La lenteur
Un algorithme de hachage de mot de passe est volontairement coûteux (en temps et parfois en mémoire). Pour l’utilisateur légitime, quelques dizaines de millisecondes à la connexion : imperceptible. Pour l’attaquant qui doit tester des milliards de candidats, ce coût rend le bruteforce économiquement irréaliste. argon2 est en plus gourmand en mémoire, ce qui neutralise l’avantage des GPU/ASIC.
3. Le coût paramétrable
Le facteur de coût (rondes bcrypt, ou mémoire/itérations argon2) est réglable : on l’augmente avec le temps, à mesure que le matériel progresse. Un hash stocké encode son propre coût, ce qui permet de re-hacher au fil des connexions quand on relève la barre.
Le choix de l’algorithme
| Algorithme | Statut | Note |
|---|---|---|
| argon2id | Recommandé | Gagnant de la compétition PHC, résistant GPU (coût mémoire). Le défaut moderne. |
| bcrypt | Solide | Éprouvé, très répandu. Attention à sa limite de longueur d’entrée (~72 octets). |
| scrypt | Solide | Coût mémoire, bon choix aussi. |
| PBKDF2 | Acceptable | Si contrainte (FIPS…), avec un nombre d’itérations élevé ; moins résistant GPU. |
| SHA/MD5 nu | Interdit | Pas conçu pour ça. |
📚 La source — Les paramètres exacts (mémoire, itérations, parallélisme argon2 ; nombre de rondes bcrypt) évoluent avec le matériel. La référence à jour est l’OWASP Password Storage Cheat Sheet : elle donne les valeurs recommandées du moment. On enseigne ici le principe (lent, salé, coût réglable, argon2id par défaut) ; règle les paramètres précis d’après cette fiche, pas d’après un chiffre mémorisé.
En pratique : utilise l’API, pas l’algo
Tu ne touches jamais à l’implémentation : les plateformes fournissent des API sûres qui gèrent sel, coût et format.
PHP — l’API idéale, sel géré automatiquement :
// Hacher a l'inscription (choisit argon2id ou bcrypt selon la config)
$hash = password_hash($motDePasse, PASSWORD_ARGON2ID);
// -> a stocker tel quel : la chaine contient l'algo, le cout ET le sel
// Verifier a la connexion (comparaison en temps constant, incluse)
if (password_verify($saisie, $hash)) {
// ok. Au passage, verifier si un rehash est necessaire :
if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
$nouveau = password_hash($saisie, PASSWORD_ARGON2ID); // re-stocker
}
}Symfony — via le PasswordHasher (config auto = meilleur algo dispo) :
// $hasher est un UserPasswordHasherInterface injecte
$user->setPassword($hasher->hashPassword($user, $plainPassword));
// verification geree par le composant Security a la connexionNode.js — bibliothèque argon2 (ou bcrypt) :
import argon2 from 'argon2'
const hash = await argon2.hash(motDePasse, { type: argon2.argon2id })
const ok = await argon2.verify(hash, saisie) // comparaison sure incluse⚠️ Piège — Ne compare jamais un hash avec
==,===ou unstrcmpnaïf sur des secrets : une comparaison qui s’arrête au premier octet différent fuit de l’information de timing (attaque temporelle). Les API ci-dessus (password_verify,argon2.verify) comparent déjà en temps constant — c’est une raison de plus de les utiliser plutôt que de bricoler. Si tu dois comparer des secrets à la main, utilisehash_equals()(PHP) /crypto.timingSafeEqual(Node).
💡 Réflexe — Le mot de passe ne doit jamais être loggué, ni transiter dans une URL, ni apparaître dans un message d’erreur. Il arrive en clair dans le corps de la requête (sur HTTPS), tu le passes directement à
password_hash/hashPassword, et il ne survit pas à la fonction. Aucune trace ailleurs.
🧭 Sur FormaCampus — FormaCampus hache tous les mots de passe en argon2id via le
PasswordHasherde Symfony (configauto), avec les paramètres recommandés par l’OWASP Cheat Sheet. À chaque connexion réussie, un rehash est déclenché si le coût a été relevé depuis. Les mots de passe ne sont jamais chiffrés (pas de « déchiffrement » possible), jamais logués, et la vérification passe par l’API standard (temps constant). Même en cas de fuite de la base, les hashes argon2id salés rendent le bruteforce massif irréaliste.
✏️ Exercices
Exercice 1 — Repère les erreurs. Ce code stocke un mot de passe. Liste tout ce qui ne va pas.
$hash = md5($_POST['password']);
$db->query("UPDATE users SET pass = '$hash' WHERE id = $id");✅ Solution
(1) MD5 : algorithme rapide et cassé, jamais pour un mot de passe. (2) Pas de sel : mots de passe identiques → hashes identiques, vulnérables aux rainbow tables. (3) Pas de coût réglable : impossible d’augmenter la difficulté. (4) Injection SQL : $hash et surtout $id concaténés dans la requête (voir Partie 6) — il faut une requête préparée. Correctif : password_hash($_POST['password'], PASSWORD_ARGON2ID) puis une requête paramétrée (UPDATE users SET pass = ? WHERE id = ?).
Exercice 2 — Vrai/Faux. « Comme on hache le mot de passe, on peut le renvoyer à l’utilisateur par email s’il l’oublie. » Réponds.
✅ Solution
Faux. Le hachage est à sens unique : le serveur ne connaît pas le mot de passe en clair, il ne peut donc pas le renvoyer (et ne le devrait jamais). Un site qui « te renvoie ton mot de passe » révèle qu’il le stocke de façon récupérable (clair ou chiffré) — un signal d’alarme. La bonne procédure est une réinitialisation : envoyer un lien à token à usage unique et durée limitée, permettant à l’utilisateur de définir un nouveau mot de passe (jamais de renvoyer l’ancien).
🧠 Quiz de révision
1. Pourquoi un SHA-256 nu est-il inadapté au stockage de mots de passe ?
Parce qu’il est trop rapide (des milliards de tests/seconde sur GPU → bruteforce massif) et, sans sel, expose les mots de passe identiques aux rainbow tables. Il faut au contraire un hachage lent et salé conçu pour ça (argon2id, bcrypt).
2. À quoi sert le sel, et faut-il le gérer soi-même ?
Le sel (aléatoire, unique par utilisateur) rend chaque hash différent même pour des mots de passe identiques, et neutralise les rainbow tables. Non, on ne le gère pas à la main : les API modernes le génèrent et le stockent automatiquement dans la chaîne de sortie.
3. Quel algorithme est recommandé aujourd’hui, et une alternative solide ?
argon2id (recommandé, résistant GPU grâce au coût mémoire). Alternatives solides : bcrypt et scrypt. On règle les paramètres d’après l’OWASP Password Storage Cheat Sheet.
4. Pourquoi comparer les hashes en « temps constant » ?
Pour éviter les attaques temporelles : une comparaison qui s’arrête au premier octet différent fuit de l’information via le temps de réponse. Les API standard (password_verify, argon2.verify, hash_equals, timingSafeEqual) comparent en temps constant.
5. Peut-on renvoyer à l’utilisateur son mot de passe oublié ?
Non : le hachage est irréversible, le serveur ne connaît pas le mot de passe en clair. Un site qui le renvoie le stocke de façon récupérable (mauvais signe). On fait une réinitialisation via un lien à token unique et éphémère, pour définir un nouveau mot de passe.
Chapitre suivant : Signatures, JWT & aléatoire — garantir l’intégrité et l’origine d’une donnée, les pièges des JWT, et générer des secrets vraiment imprévisibles.