Pourquoi les mots de passe nécessitent un traitement particulier
Tous les autres secrets de votre système peuvent être renouvelés : clés API, tokens, certificats. Les mots de passe sont différents. Vous ne les détenez jamais sous une forme permettant de les régénérer, les utilisateurs les réutilisent sur plusieurs sites, et dès que votre base de données fuite, l’attaquant a tout le temps nécessaire pour tenter de les deviner hors ligne.
Le hachage de mot de passe consiste à stocker une transformation unidirectionnelle du mot de passe au lieu du mot de passe lui-même. Lorsqu’un utilisateur se connecte, vous appliquez la même transformation et comparez les résultats. Vous ne connaissez jamais le mot de passe, et quiconque volerait la table ne le connaîtrait pas non plus.
Ce n’est pas le moment de vouloir être original. Les règles sont simples et bien établies, et c’est en les transgressant que les failles de sécurité deviennent catastrophiques.
Le hachage n’est pas du chiffrement
Le chiffrement est réversible. Si vous chiffrez les mots de passe, la clé se trouve quelque part dans votre système, et toute personne qui y accède — via une fuite, une sauvegarde, une variable d’environnement mal configurée ou un initié — peut lire tous les mots de passe d’un coup. Il n’existe aucun scénario où vous avez besoin du mot de passe original, donc la réversibilité représente un risque pur.
Une fonction de hachage prend une entrée et produit un condensat (digest) de longueur fixe, et il n’existe aucun moyen pratique de revenir en arrière. Pour les mots de passe, un hachage simple ne suffit toujours pas : les fonctions rapides comme SHA-256 sont conçues pour être véloces, ce qui est précisément ce qu’un attaquant recherche. Un GPU peut tester des milliards de combinaisons par seconde.
Vous avez besoin d’une fonction qui soit délibérément lente et memory-hard, afin que les tentatives de devinette soient coûteuses à grande échelle.
Salt : même mot de passe, hash différent
Sans salt, deux utilisateurs ayant le mot de passe hunter2 produiront le même condensat (digest). Un attaquant peut précalculer un tableau géant de mots de passe courants et de leurs hashs — une rainbow table — et trouver des correspondances instantanément. Il peut également voir d’un coup d’œil quels comptes partagent le même mot de passe.
Un salt (ou grain de sel) est une valeur aléatoire unique générée pour chaque mot de passe et stockée aux côtés du condensat. Ce n’est pas un secret ; son seul rôle est de rendre chaque hash unique. Désormais, l’attaquant doit s’attaquer à chaque compte séparément, et le précalcul devient inutile.
Les bibliothèques modernes génèrent le salt pour vous et l’encodent, avec les paramètres, dans une seule et même chaîne de caractères. Il vous suffit de stocker cette chaîne telle quelle.
Choisir un algorithme
Trois fonctions valent la peine d’être connues. Elles sont toutes adaptatives : le coût peut être augmenté à mesure que le matériel s’améliore.
Argon2id
Le vainqueur de la Password Hashing Competition de 2015 et la recommandation actuelle. Il est “memory-hard”, ce qui signifie qu’un attaquant doit investir dans la mémoire autant que dans la puissance de calcul, réduisant ainsi l’avantage des GPU et des ASIC. Argon2id équilibre la résistance aux attaques par canal auxiliaire et aux attaques GPU, et constitue le choix par défaut idéal pour les nouveaux systèmes.
scrypt
Standardisé dans la RFC 7914 et également “memory-hard”, scrypt est un choix solide lorsque Argon2 n’est pas disponible. Il expose le coût, la taille des blocs et le parallélisme, ce qui permet de l’ajuster, mais ses paramètres sont plus faciles à mal configurer que ceux d’Argon2.
bcrypt
Sorti en 1999 et encore omniprésent, bcrypt est bien compris et éprouvé. Son facteur de coût double la charge de travail à chaque incrément. Le principal bémol est qu’il tronque l’entrée à 72 octets, donc les phrases de passe très longues sont silencieusement coupées — effectuez un pré-hash si cela est important pour vous.
Quel que soit votre choix, n’utilisez jamais MD5, SHA-1 ou SHA-256 pur. Ce ne sont pas les bons outils, et empiler des itérations par-dessus constitue un schéma artisanal en lequel personne ne devrait avoir confiance.
Ajuster le facteur de travail (work factor)
Le coût doit équilibrer deux contraintes. S’il est trop bas, le hash devient vulnérable aux attaques par force brute ; s’il est trop élevé, un pic de tentatives de connexion peut se transformer en vecteur de déni de service (DoS) contre votre propre CPU.
Ajustez-le pour qu’un seul hash prenne environ 100 à 500 millisecondes sur votre matériel de production. Mesurez le point de terminaison (endpoint) de connexion sous une charge réaliste, et non via une simple boucle de benchmark, puis fixez cette valeur en vous basant sur vos données. Revoyez ensuite ce paramètre périodiquement : à paramètres égaux, la sécurité s’affaiblit chaque année à mesure que le matériel s’améliore.
// Argon2id parameters (memory in KiB, iterations, parallelism)
await hash(password, { memoryCost: 19456, timeCost: 2, parallelism: 1 });
Stockage et vérification
L’inscription et la connexion sont les deux seuls endroits où vous manipulez un mot de passe.
import { hash, verify } from "@node-rs/argon2";
// Registration: hash before the password ever reaches the database.
const digest = await hash(password);
await db.users.insert({ email, passwordHash: digest });
// Login: verify against the stored digest.
const user = await db.users.findByEmail(email);
const ok = user && (await verify(user.passwordHash, password));
if (!ok) return res.status(401).json({ error: "invalid_credentials" });
Renvoyez la même erreur, que l’e-mail ou le mot de passe soit incorrect. Un message tel que « utilisateur inexistant » permettrait à un attaquant d’énumérer les comptes.
Attaques temporelles et comparaison sécurisée
Comparer deux hashs avec === s’arrête au premier octet différent. Le temps d’exécution dépend donc de la partie correcte de la tentative, et un attaquant patient peut mesurer cet écart pour reconstruire une valeur. C’est un canal étroit, mais la comparaison de mots de passe est précisément l’endroit où cela devient critique.
Utilisez la fonction de vérification de la bibliothèque, qui effectue la comparaison en temps constant. Ne comparez jamais les digests vous-même, et ne comparez jamais le mot de passe en clair.
Mettre à jour les hashs au fil du temps
Vos besoins évolueront : le coût de calcul augmentera, ou vous migrerez de bcrypt vers Argon2. Comme vous n’avez jamais stocké le mot de passe en clair, vous ne pouvez effectuer le rehash qu’au moment de la connexion, lorsque le mot de passe se trouve brièvement en mémoire.
Stockez l’algorithme et les paramètres avec chaque digest — la chaîne encodée le fait déjà — et vérifiez-les à chaque connexion réussie :
if (await verify(stored, password)) {
if (needsRehash(stored)) {
await db.users.update(user.id, { passwordHash: await hash(password) });
}
// continue the login
}
Les utilisateurs qui ne se connectent plus conservent leur ancien hash, ce qui ne pose aucun problème ; ils restent protégés. Au fil du temps, les comptes actifs migrent naturellement.
La politique de mots de passe en pratique
L’algorithme vous protège après une faille. La politique réduit la probabilité qu’une faille survienne.
- Privilégiez la longueur plutôt que la complexité. Une phrase de passe est plus efficace qu’
P@ssw0rd!. - Vérifiez les nouveaux mots de passe par rapport aux listes de fuites connues, comme l’API k-anonymity de Have I Been Pwned.
- N’imposez pas de rotation arbitraire. L’expiration forcée conduit à des schémas de type
Summer2026!. - Limitez le nombre de tentatives de connexion et de réinitialisation (rate-limiting), et ajoutez un backoff exponentiel ou un verrouillage du compte.
- Utilisez un flux de réinitialisation générique avec des jetons à usage unique et à expiration limitée, et n’envoyez jamais de mot de passe par e-mail.
Bonnes pratiques
- Utilisez Argon2id, scrypt ou bcrypt pour le hachage — jamais un algorithme de hachage généraliste rapide.
- Laissez la bibliothèque générer et stocker le sel (salt).
- Ajustez le coût pour obtenir un temps de traitement entre 100 et 500 ms, et augmentez-le progressivement avec le temps.
- Effectuez la vérification avec la fonction en temps constant de la bibliothèque.
- Re-hachez le mot de passe lors d’une connexion réussie si les paramètres ont changé.
- Retournez des erreurs génériques et limitez le nombre de tentatives (rate-limiting).
- Effectuez le hachage côté serveur ; jamais côté client.
Erreurs courantes
- Chiffrer les mots de passe ou les stocker en texte brut « temporairement ».
- Utiliser SHA-256 ou MD5 avec seulement quelques itérations et appeler cela du hachage.
- Utiliser un sel global unique, ou réutiliser le même sel pour plusieurs comptes.
- Comparer des hashs avec
==ou===. - Journaliser les mots de passe ou les inclure dans des rapports d’erreur.
- Limiter la longueur des mots de passe à une valeur trop basse, ce qui pénalise les phrases de passe.
- Oublier de mettre à jour le facteur de coût pendant plusieurs années.
Et après ?
Les identifiants ne sont que la première étape. Une fois l’utilisateur vérifié, vous devez pouvoir conserver cet état : consultez le guide sur Session Auth pour les sessions basées sur les cookies, ou le guide JWT pour les jetons sans état (stateless). Si vous préférez ne pas gérer les mots de passe du tout, OAuth 2.0 et OpenID Connect permettent de déléguer l’authentification à un fournisseur d’identité.