bcrypt vs Argon2 vs scrypt : hachage de mots de passe en 2026
Réponse courte : pour tout nouveau projet en 2026, prenez Argon2id avec m=19456, t=2, p=1. C’est la base de référence du OWASP Password Storage Cheat Sheet, et c’est aujourd’hui l’algorithme de hachage de mots de passe qui résiste le mieux aux GPU et aux attaques par canaux auxiliaires.
Si Argon2 n’est pas disponible dans votre stack (rare, mais ça arrive sur certains environnements embarqués ou anciens runtimes), prenez scrypt avec N=2^17, r=8, p=1. N’utilisez bcrypt avec cost=12 que si vous êtes coincé avec un système hérité qui parle déjà bcrypt et que vous ne pouvez pas introduire de nouvelle dépendance. Restez sur PBKDF2-HMAC-SHA-256 avec 600 000 itérations quand la conformité FIPS-140 est obligatoire.
| Algorithme | Paramètres OWASP 2026 | Quand le choisir |
|---|---|---|
| Argon2id | m=19456 KiB, t=2, p=1 | Par défaut pour les nouveaux projets |
| scrypt | N=2^17, r=8, p=1 | Argon2 indisponible |
| bcrypt | cost=12 (min 10) | Systèmes hérités uniquement |
| PBKDF2 | HMAC-SHA-256, 600k itérations | FIPS-140 requis |
Le reste de cet article explique d’où viennent ces chiffres, comment les ajuster à votre matériel, et comment migrer sans forcer une réinitialisation des mots de passe. Besoin de générer des mots de passe de test solides pour vos benchmarks ? Utilisez le générateur de mot de passe aléatoire. Pour le contexte plus large, consultez le guide des bonnes pratiques de sécurité web.
Pourquoi le hachage de mots de passe diffère du hachage général
Vues de l’extérieur, les fonctions de hachage se ressemblent toutes : on entre des données, il en sort un condensé de longueur fixe, et on ne peut pas l’inverser. Mais les objectifs de conception pour « hacher cet ISO de 4 Go » et « hacher ce mot de passe de 12 caractères » sont diamétralement opposés. Le premier doit être aussi rapide que le silicium le permet. Le second doit être aussi lent que votre budget de latence de connexion le tolère.
Confondre les deux, c’est exactement comme ça qu’une fuite se transforme en prises de contrôle de comptes.
Pourquoi MD5 et SHA-256 ne suffisent pas pour les mots de passe
Les fonctions de hachage généralistes comme MD5, SHA-1 et SHA-256 ont été conçues pour le débit. Elles traitent des gigaoctets par seconde sur des CPU grand public et des dizaines de gigaoctets par seconde sur des GPU. C’est parfait pour les sommes de contrôle de fichiers et l’adressage de contenu, et catastrophique pour les mots de passe.
Les benchmarks Hashcat sur une seule RTX 4090 affichent environ 164 GH/s pour MD5 et 22 GH/s pour SHA-256 en 2024. Un mot de passe de huit caractères en alphanumérique minuscule (36^8 ≈ 2,8 × 10^12 candidats) tombe face à un seul GPU en moins d’une minute contre MD5 et en moins de deux minutes contre SHA-256. Une base de données compromise qui stocke sha256(password) est en pratique en clair.
Le sel ne vous sauve pas non plus. Il empêche les tables arc-en-ciel pré-calculées de fonctionner, mais ne ralentit en rien une attaque par compte : l’attaquant hache chaque candidat concaténé avec le sel divulgué et c’est tout.
Pour les sommes de contrôle sans enjeu de sécurité, MD5 et SHA-256 restent utiles, c’est précisément à ça que servent des outils comme le générateur de hash MD5. Pour savoir quand chaque algorithme est approprié, lisez MD5 vs SHA-256 : comparaison des algorithmes de hachage. Mais pour les mots de passe, il vous faut un hachage volontairement lent.
Les trois propriétés d’un hachage de mots de passe moderne
Un hachage de mots de passe qu’on peut déployer en 2026 a trois propriétés :
- Lent par conception, avec un facteur de travail réglable. Une connexion devrait prendre 100 à 500 ms : assez rapide pour que les utilisateurs ne s’en aperçoivent pas, assez lent pour qu’un attaquant hors ligne brûle des jours par million de tentatives. Le facteur de travail doit être un paramètre, pour pouvoir le pousser à mesure que le matériel progresse.
- Sel par enregistrement. Un sel aléatoire unique par mot de passe met en échec les tables arc-en-ciel et oblige l’attaquant à attaquer chaque compte un par un. Les algorithmes modernes génèrent et intègrent le sel dans la chaîne de sortie pour vous.
- Mémoire-hard. Les GPU et les ASIC sont rapides en calcul, mais coûteux en mémoire à haute bande passante. Un algorithme qui exige des dizaines de MiB par hachage oblige l’attaquant à provisionner de la RAM proportionnelle à son parallélisme, ce qui ruine la rentabilité des fermes de GPU.
bcrypt remplit (1) et (2) mais pas (3). scrypt fut le premier algorithme à cocher les trois cases. Argon2 a affiné la conception et remporté la Password Hashing Competition. La section suivante détaille chacun.
Les trois algorithmes : architecture et compromis
bcrypt, basé sur Blowfish, time-hard
bcrypt a été conçu en 1999 par Niels Provos et David Mazières pour OpenBSD. Il s’appuie sur le chiffre Blowfish, avec une phase d’initialisation de clé coûteuse (« EksBlowfish ») répétée 2^cost fois. Le seul paramètre réglable est le facteur de coût (aussi appelé « log rounds ») : chaque incrément double le travail. Un hachage cost=10 effectue 1 024 key schedules, cost=14 en effectue 16 384.
Un hachage bcrypt ressemble à ceci :
$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
│ │ │ │
│ │ │ └─ hachage base64 de 31 caractères
│ │ └─ sel base64 de 22 caractères
│ └─ facteur de coût (12)
└─ identifiant d'algorithme ($2b$ = bcrypt v2)
Le format est auto-descriptif : verify() lit le coût et le sel depuis la chaîne stockée, sans avoir besoin de colonnes séparées.
Les inconvénients sont réels. L’empreinte mémoire de bcrypt tourne autour de 4 KiB, assez petite pour qu’un GPU haut de gamme exécute des milliers de cœurs bcrypt en parallèle. Et bcrypt tronque silencieusement l’entrée à 72 octets. Une phrase de passe de 100 caractères a la même sécurité que ses 72 premiers octets. Le coût maximum est 31, mais tout ce qui dépasse ~16 commence à pénaliser la latence de connexion sur du matériel grand public.
scrypt, le pionnier mémoire-hard
scrypt a été publié en 2009 par Colin Percival pour le service de sauvegarde Tarsnap et standardisé via la RFC 7914 en 2016. Il a introduit l’idée de dureté mémoire : l’algorithme remplit un grand tampon de données pseudo-aléatoires, puis lit à des positions aléatoires, forçant toute implémentation à allouer réellement la mémoire.
scrypt prend trois paramètres :
- N : coût CPU/mémoire (doit être une puissance de 2)
- r : taille de bloc en octets (multiplicateur sur la mémoire et les tours de mélange)
- p : parallélisme (calculs indépendants, surtout utile pour augmenter le temps CPU sans augmenter la mémoire)
L’utilisation mémoire est d’environ 128 × N × r octets. Avec les valeurs recommandées par OWASP N=2^17, r=8, ça donne 128 × 131072 × 8 = 134 217 728 octets, soit exactement 128 MiB par hachage.
scrypt est aussi une fonction de dérivation de clé, pas uniquement un hachage de mots de passe. On le retrouve dans les portefeuilles de cryptomonnaies, le chiffrement de disque complet, et la preuve de travail originale de Litecoin. Ce double rôle est pratique quand vous avez besoin à la fois de stockage de mots de passe et de dérivation de clés dans une même bibliothèque.
Argon2 (id/i/d), vainqueur de la Password Hashing Competition
La Password Hashing Competition s’est déroulée de 2013 à 2015, évaluant 24 algorithmes candidats sur la dureté mémoire, la résistance aux canaux auxiliaires et la simplicité d’implémentation. Argon2 a gagné. Il a été standardisé via la RFC 9106 en 2021.
Argon2 a trois variantes. Les différences se résument à la façon dont la mémoire est adressée pendant le mélange :
- Argon2d utilise des adresses mémoire dépendantes des données. Ça maximise la résistance aux attaques GPU et ASIC mais fuit de l’information par les canaux auxiliaires de timing du cache. Adapté à la preuve de travail des cryptomonnaies, pas à l’authentification.
- Argon2i utilise des adresses indépendantes des données. Sûr face aux canaux auxiliaires, mais un peu plus faible face aux attaques par compromis temps/mémoire sur GPU.
- Argon2id est un hybride : la première moitié de la première passe utilise l’indexation Argon2i (sûre face aux canaux auxiliaires), et le reste utilise l’indexation Argon2d (résistante aux GPU). La RFC 9106 recommande explicitement Argon2id pour le hachage de mots de passe, et OWASP aussi.
Argon2 prend trois paramètres :
- m : mémoire en KiB
- t : coût en temps (nombre de passes sur le tampon mémoire)
- p : parallélisme (nombre de voies traitées simultanément)
Un hachage Argon2id utilise le format de chaîne PHC et ressemble à ça :
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
Comme avec bcrypt, tous les paramètres sont intégrés dans la chaîne, donc verify() n’a pas besoin d’une table de paramètres séparée.
Paramètres recommandés OWASP 2026
Le OWASP Password Storage Cheat Sheet est la référence canonique. Les chiffres ci-dessous correspondent à ses recommandations actuelles. Ils sont conservateurs (calibrés pour un serveur web typique avec un budget de latence de connexion de 100 à 500 ms) et vous devriez quand même mesurer sur votre propre matériel avant la mise en production.
Paramètres Argon2id : premier choix
Recommandation de base OWASP : m=19456 (19 MiB), t=2, p=1.
Si votre serveur dispose de plus de RAM, vous pouvez répartir le travail entre mémoire et temps. La RFC 9106 publie des profils équivalents, OWASP recommande l’un de ceux-ci :
| memoryCost (m) | timeCost (t) | parallélisme (p) | RAM par hachage |
|---|---|---|---|
| 47104 | 1 | 1 | 46 MiB |
| 19456 | 2 | 1 | 19 MiB (base) |
| 12288 | 3 | 1 | 12 MiB |
| 9216 | 4 | 1 | 9 MiB |
| 7168 | 5 | 1 | 7 MiB |
Règle de réglage empirique. Choisissez d’abord m en fonction de votre budget RAM lors d’un pic de connexions concurrentes. Si vous attendez 100 connexions simultanées et avez 4 GiB à consacrer, ça fait 40 MiB par hachage. Augmentez ensuite t jusqu’à ce qu’une seule vérification prenne 100 à 500 ms sur votre CPU de production. Laissez p=1 sauf raison spécifique liée au multicœur de le changer (la plupart des frameworks web donnent déjà à chaque requête son propre thread).
Paramètres scrypt : quand Argon2 n’est pas disponible
Recommandation OWASP : N=2^17 (131072), r=8, p=1, soit 128 MiB par hachage.
Si 128 MiB par connexion concurrente, c’est trop pour votre serveur, OWASP autorise des profils plus faibles :
| N | r | p | RAM par hachage |
|---|---|---|---|
| 2^17 | 8 | 1 | 128 MiB (préféré) |
| 2^16 | 8 | 1 | 64 MiB |
| 2^15 | 8 | 1 | 32 MiB |
N doit être une puissance de deux. Augmenter r augmente proportionnellement la mémoire et le travail CPU, augmenter p augmente le travail CPU sans augmenter la mémoire par instance. Pour le hachage de mots de passe, laissez r et p à leurs valeurs par défaut et ne réglez que N.
bcrypt : facteur de coût 10+ pour le legacy uniquement
OWASP ne recommande plus bcrypt pour les nouveaux projets, mais il est encore partout : Devise, Spring Security, ASP.NET Identity, et d’innombrables systèmes d’authentification maison l’utilisent par défaut.
Si vous êtes coincé avec bcrypt, les règles sont :
- Facteur de coût bcrypt minimum : 10. En dessous de 10, c’est assez rapide pour qu’un seul GPU termine une base de données divulguée en quelques jours.
- Recommandé : 12 à 14, selon le matériel. Sur un serveur x86 moderne,
cost=12prend environ 250 ms par hachage,cost=13prend 500 ms. - Visez 100 à 300 ms par vérification sur votre matériel de production. Mesurez, ne devinez pas.
- Souvenez-vous de la limite d’entrée à 72 octets. Si les utilisateurs peuvent choisir des phrases de passe, pré-hachez avec SHA-256 (voir la FAQ).
La résistance aux GPU de bcrypt est plafonnée par son empreinte mémoire de 4 KiB. Aucun facteur de coût bcrypt n’égalera jamais la dureté mémoire d’Argon2id, prenez Argon2id quand vous le pouvez.
Une référence pratique : sur un serveur EPYC 2024, bcrypt(cost=12) tourne en environ 250 ms, sur un ordinateur portable haut de gamme, plutôt 350 ms. Si vos chiffres sortent d’un ordre de grandeur de la fourchette 100 à 500 ms, vérifiez si votre bibliothèque exécute réellement bcrypt natif ou retombe sur un polyfill JavaScript lent (certains bundlers retirent les dépendances natives dans les builds serverless).
PBKDF2 : voie de conformité FIPS-140
PBKDF2 (RFC 8018) est l’algorithme du dernier recours côté guidance de sécurité. Il est plus ancien que bcrypt, il n’est pas mémoire-hard, et il tombe face aux attaques GPU plus vite que les trois précédents. Mais c’est la seule primitive de hachage de mots de passe validée FIPS-140, ce qui compte pour le gouvernement fédéral, la santé HIPAA, et certains déploiements financiers.
Quand vous avez besoin de PBKDF2, utilisez :
- HMAC-SHA-256 comme PRF (pas SHA-1, pas SHA-256 nu sans HMAC)
- 600 000 itérations minimum (base de référence OWASP 2026)
- Au moins un sel aléatoire de 16 octets par mot de passe
Si FIPS ne s’applique pas à vous, prenez Argon2id. La conception à sortie fixe et mémoire fixe de PBKDF2 fait que chaque dollar de silicium GPU acheté par un attaquant se traduit directement en plus de tentatives de mots de passe par seconde.
Le SP 800-63B du NIST qualifie PBKDF2-HMAC d’« approuvé » pour le hachage de mots de passe, mais s’arrête avant de le recommander par rapport aux alternatives mémoire-hard. Lisez-le comme ça : le NIST autorise PBKDF2 parce que le retirer invaliderait tous les déploiements gouvernementaux hérités, pas parce que c’est le meilleur choix pour un projet vierge.
Cadre de décision : quel algorithme choisir ?
Tableau comparatif
| Dimension | bcrypt | scrypt | Argon2id | PBKDF2 |
|---|---|---|---|---|
| Mémoire-hard | Non | Oui | Oui | Non |
| Résistance GPU | Moyenne | Élevée | Très élevée | Faible |
| Résistance canaux auxiliaires | Moyenne | Moyenne | Élevée (id) | Moyenne |
| Complexité des paramètres | 1 (cost) | 3 (N, r, p) | 3 (m, t, p) | 1 (itérations) |
| Maturité des bibliothèques | Excellente | Bonne | Bonne | Excellente |
| Limite de longueur d’entrée | 72 octets | Aucune | Aucune | Aucune |
| Standardisation | de facto | RFC 7914 | RFC 9106 | RFC 8018 |
| Statut OWASP 2026 | Hérité uniquement | Alternative | Premier choix | FIPS uniquement |
Argon2id par défaut
Pour un nouveau projet (application web typique, stack Node/Python/Go/Rust/JVM moderne, sans contrainte FIPS), utilisez Argon2id avec m=19456, t=2, p=1. Vous obtenez la meilleure résistance aux GPU et aux canaux auxiliaires disponible, un format à paramètres intégrés qui survit aux mises à jour de bibliothèques, et aucune surprise de longueur d’entrée. L’écosystème de bibliothèques est mature : argon2 sur npm, argon2-cffi sur PyPI, golang.org/x/crypto/argon2, la crate argon2 sur crates.io, tous maintenus et benchmarkés.
Quand prendre scrypt ou bcrypt à la place
Prenez scrypt quand Argon2 n’est pas disponible dans votre runtime (vraiment rare en 2026, même Cloudflare Workers et Deno l’ont maintenant), ou quand vous avez déjà un système basé sur scrypt en production et que le coût de migration dépasse l’écart de sécurité. scrypt reste un algorithme solide, il lui manque juste le polissage anti-canaux-auxiliaires d’Argon2id.
Prenez bcrypt quand vous maintenez un système hérité, que vous avez une exigence stricte de minimisation des dépendances (pas de code natif, pas de paquets supplémentaires), et que la limite d’entrée à 72 octets est acceptable pour votre base d’utilisateurs. bcrypt est déployé à l’échelle d’Internet depuis deux décennies, ses modes de défaillance sont bien compris.
Prenez PBKDF2 quand le régulateur l’exige. C’est la seule raison. Si votre auditeur accepte Argon2id (ce qui devient de plus en plus fréquent pour les charges de travail hors FIPS), prenez Argon2id.
Erreurs courantes à éviter
La plupart des fuites de stockage de mots de passe de la dernière décennie remontent à un petit ensemble d’erreurs d’ingénierie récurrentes. Aucune n’est exotique, toutes se rattrapent en relisant votre code d’authentification avec la liste ci-dessous sous les yeux.
- Hacher des mots de passe avec SHA-256 ou MD5 brut. C’est la plus grosse défaillance de stockage de mots de passe. Voyez MD5 vs SHA-256 pour comprendre pourquoi ces algorithmes ne conviennent pas aux mots de passe.
- Réutiliser un seul sel global pour tous les utilisateurs. Un sel doit être unique par enregistrement. Argon2 et bcrypt en génèrent un pour vous, ne le surchargez pas.
- Régler le temps de hachage en dessous de 50 ms. Vous avez troqué de la sécurité contre un gain de vitesse imperceptible pour l’utilisateur. Visez 100 à 500 ms.
- Régler le temps de hachage au-dessus de 1 seconde. Vous avez créé un vecteur de déni de service contre votre propre endpoint de connexion. Plafonnez à ~500 ms.
- Hacher les mots de passe côté client et envoyer le condensé au serveur. Le hachage devient le mot de passe. Quiconque vole la base de données peut s’authentifier sans jamais l’inverser. Hachez toujours côté serveur.
- Stocker les paramètres de l’algorithme dans une colonne séparée. Le format de chaîne PHC les intègre dans le hachage. Utilisez-le.
- Logger des mots de passe ou des hachages dans la gestion d’erreurs. Les deux appartiennent à l’utilisateur, pas à votre agrégateur de logs. Effacez-les au niveau du parsing de la requête, avant qu’ils n’atteignent le moindre logger.
- Traiter les exceptions de
verify()comme des échecs d’authentification. Une bibliothèque qui lève une exception sur un hachage stocké malformé devrait remonter l’erreur, pas retomber silencieusement sur « mauvais mot de passe ». Distinguez « mauvais mot de passe » (renvoyer 401) et « hachage stocké corrompu » (renvoyer 500 et alerter l’astreinte).
Implémentation concrète
Argon2id en Node.js
Le paquet argon2 (bindings natifs vers l’implémentation de référence) est le choix canonique sous Node :
import argon2 from 'argon2';
// Hachage à l'inscription ou au changement de mot de passe
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // 19 MiB
timeCost: 2,
parallelism: 1,
});
// → '$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>'
// Vérification à la connexion
const ok = await argon2.verify(hash, candidate);
if (!ok) throw new Error('Invalid credentials');
// Détecter des paramètres obsolètes et re-hacher après une connexion réussie
if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) {
const upgraded = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, { password_hash: upgraded });
}
L’étape needsRehash est la clé d’une migration sur le long terme : chaque connexion réussie devient une occasion de mettre à niveau le hachage stocké vers les paramètres actuels, sans déranger l’utilisateur.
Le même schéma en Python avec argon2-cffi :
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)
# Hachage
stored = ph.hash(password)
# Vérification
try:
ph.verify(stored, candidate)
except VerifyMismatchError:
raise ValueError('Invalid credentials')
# Re-hachage à la mise à jour des paramètres
if ph.check_needs_rehash(stored):
stored = ph.hash(candidate)
En Go avec golang.org/x/crypto/argon2 :
import (
"crypto/rand"
"golang.org/x/crypto/argon2"
)
func hashPassword(password string) ([]byte, []byte) {
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
return hash, salt
}
La bibliothèque standard de Go ne fournit pas d’encodeur au format PHC. Si vous utilisez la primitive argon2.IDKey directement, c’est à vous d’encoder les paramètres et le sel à côté du hachage. La plupart des projets Go utilisent un wrapper comme github.com/alexedwards/argon2id à cette fin.
Rust avec la crate argon2 est tout aussi idiomatique :
use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}};
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default(); // Argon2id, m=19456, t=2, p=1 par défaut
let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();
// À la vérification
let parsed = argon2::password_hash::PasswordHash::new(&hash)?;
argon2.verify_password(candidate.as_bytes(), &parsed)?;
Dans les trois runtimes, la chaîne produite est interchangeable : un hachage créé sous Node se vérifie proprement en Python ou en Rust. Cette compatibilité multi-runtime fait d’Argon2 un pari plus sûr pour les architectures polyglottes que les wrappers spécifiques à un algorithme.
Schéma de migration bcrypt vers Argon2id
Vous n’avez presque jamais l’occasion de vider la table des utilisateurs et de repartir de zéro. Le bon schéma de migration est le même que celui utilisé dans la section MD5 vers bcrypt de la FAQ de notre générateur de hash : une mise à niveau douce, pilotée par les connexions.
Ajoutez une colonne qui trace l’algorithme :
ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt';
À la connexion, dispatchez vers le bon vérificateur :
async function verifyAndMaybeRehash(user, candidate) {
let ok;
if (user.password_algo === 'argon2id') {
ok = await argon2.verify(user.password_hash, candidate);
} else if (user.password_algo === 'bcrypt') {
ok = await bcrypt.compare(candidate, user.password_hash);
if (ok) {
// Vérification legacy réussie → re-hachage avec Argon2id
const newHash = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, {
password_hash: newHash,
password_algo: 'argon2id',
});
}
}
return ok;
}
Fixez une fenêtre de fin de vie de 6 à 12 mois. Envoyez un email « votre mot de passe est stocké via une méthode obsolète, connectez-vous pour la mise à niveau » au neuvième mois. Après 12 mois, les comptes encore sous bcrypt exigent une réinitialisation forcée à la prochaine connexion. Les utilisateurs actifs migrent sans s’en apercevoir, les comptes inactifs subissent un événement unique de friction.
Le même schéma fonctionne pour migrer depuis scrypt ou PBKDF2. Le seul état nécessaire est la colonne password_algo.
Pepper, limites de longueur et pièges d’encodage
Quelques arêtes vives qui mordent les déploiements réels :
Pepper. Un pepper est un secret au niveau de l’application ajouté à chaque mot de passe avant hachage, stocké séparément de la base de données (dans un KMS, une variable d’environnement ou Hashicorp Vault). Si votre base de données fuit mais que votre secret applicatif tient, les hachages divulgués sont inattaquables sans le pepper. Appliquez-le via un HMAC, pas par concaténation :
import { createHmac } from 'crypto';
const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest();
const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ });
Faites tourner le pepper rarement (ça exige un re-hachage), mais prenez en charge la rotation en versionnant : PEPPER_V2, avec un repli sur PEPPER_V1 à la vérification.
Limite des 72 octets de bcrypt. Si vous devez utiliser bcrypt et que vous voulez gérer des mots de passe de longueur arbitraire, pré-hachez avec SHA-256 et encodez en base64 (en évitant les octets NUL embarqués que bcrypt gère aussi de façon incohérente) :
import { createHash } from 'crypto';
const prepped = createHash('sha256').update(password, 'utf8').digest('base64');
const hash = await bcrypt.hash(prepped, 12);
La même transformation prepped doit être exécutée à la vérification. Documentez ça dans votre code d’authentification avec un commentaire géant, votre vous-futur remerciera votre vous-présent.
Normalisation UTF-8. La chaîne "café" peut être encodée soit comme c-a-f-é (4 points de code, NFC), soit comme c-a-f-e + accent aigu combinant (5 points de code, NFD). Elles paraissent identiques à l’œil mais produisent des hachages différents. Normalisez toujours en NFC avant de hacher :
const normalized = password.normalize('NFC');
Ça mord plus souvent qu’on ne le croit, surtout avec les claviers mobiles et les copier-coller depuis des PDF.
Ne pré-hachez jamais côté client. Un hachage calculé côté client envoyé au serveur devient le nouveau mot de passe. Quiconque lit votre base de données peut s’authentifier. Hachez côté serveur, point. Les JWT ne changent rien à ça : voyez comment décoder un JWT pour ce que les JWT authentifient et ce qu’ils n’authentifient pas.
Mesurez sur du matériel de production, pas sur votre laptop. Un laptop Intel de 13e génération qui exécute Argon2id avec m=19456, t=2, p=1 termine en environ 35 ms. Les mêmes paramètres sur une instance EC2 t3.small prennent plutôt 180 ms, sur un Raspberry Pi 4, plus de 600 ms. Choisissez le matériel qui tournera réellement en production, chronométrez 1 000 vérifications, et ajustez à partir de la médiane. La variance de latence de connexion due aux démarrages à froid des conteneurs serverless mérite aussi d’être mesurée : les cold starts Lambda peuvent ajouter 200 à 800 ms sans rapport avec le hachage.
FAQ
Quelle est la différence entre hachage et chiffrement de mots de passe ?
Le hachage est unidirectionnel : on calcule une empreinte de longueur fixe qui ne peut pas être inversée pour retrouver l’entrée. Le chiffrement est bidirectionnel : avec la bonne clé, on peut déchiffrer pour retrouver l’original. Les mots de passe doivent être hachés, pas chiffrés. Un serveur ne devrait pas pouvoir retrouver le mot de passe d’aucun utilisateur. Comme ça, une fuite de base de données ne devient pas une fuite d’identifiants.
Pourquoi ne puis-je pas simplement utiliser SHA-256 pour les mots de passe ?
SHA-256 est conçu pour la vitesse. Un GPU moderne calcule 22 milliards de hachages SHA-256 par seconde, donc un mot de passe de 8 caractères en minuscules issu d’une base divulguée tombe en quelques minutes. Un hachage de mots de passe a besoin de trois propriétés que SHA-256 n’a pas : une exécution volontairement lente, un sel par enregistrement, et la dureté mémoire. Le principe du compromis est le même que celui expliqué dans la rubrique « N’utilisez pas MD5 pour la sécurité » de notre générateur de hash, et vous pouvez en lire plus sur la façon dont les attaquants transforment des hachages faibles en clair dans l’entropie des mots de passe expliquée.
bcrypt est-il toujours sûr en 2026 ?
bcrypt en lui-même n’a pas été cassé. Le key schedule basé sur Blowfish reste cryptographiquement solide. Ce qui a changé, c’est le modèle de menace : les GPU et ASIC font de l’absence de dureté mémoire de bcrypt une faiblesse réelle comparée à Argon2id. La position OWASP 2026 est que bcrypt est acceptable pour les systèmes hérités avec un cost ≥ 10, mais que les nouveaux projets devraient prendre Argon2id.
Argon2i vs Argon2d vs Argon2id : lequel utiliser ?
Prenez Argon2id. La RFC 9106 le spécifie comme variante recommandée pour le hachage de mots de passe. Argon2i est indépendant des données (sûr face aux canaux auxiliaires mais plus faible face aux attaques par compromis sur GPU). Argon2d est dépendant des données (fort face aux GPU mais vulnérable aux canaux auxiliaires de timing du cache). Argon2id est un hybride qui obtient les deux propriétés pour le prix d’une.
Comment choisir les paramètres Argon2id pour mon application ?
Partez de la base OWASP : m=19456, t=2, p=1. Mesurez ensuite sur votre CPU de production et ajustez :
- Décidez de votre budget RAM par connexion (par exemple 50 MiB en concurrence de pointe).
- Réglez
mà cette valeur ou en dessous. - Lancez
argon2.hash()en boucle et mesurez le temps écoulé. - Augmentez
tjusqu’à ce que la médiane se situe entre 100 et 500 ms.
Laissez p=1 sauf si vous avez profilé et que vous savez que le parallélisme multi-voies aide votre runtime. Pour les serveurs d’authentification à fort trafic, pencher vers un t plus élevé et un m plus bas donne souvent une meilleure marge de RAM.
C’est quoi la limite de 72 octets de bcrypt et comment gérer les phrases de passe longues ?
bcrypt injecte son entrée dans le key schedule de Blowfish, qui tronque à 72 octets. Une phrase de passe de 150 caractères a la même sécurité que ses 72 premiers octets, le reste est ignoré. La parade : pré-hachez avec SHA-256 (32 octets) ou SHA-512 (64 octets), encodez le condensé en base64 pour éviter les octets NUL, et passez ça à bcrypt. Argon2id et scrypt n’ont aucune limite de ce type, ils acceptent directement des entrées de longueur arbitraire.
Puis-je migrer bcrypt vers Argon2 sans forcer de réinitialisation des mots de passe ?
Oui. Le schéma : stockez les deux algorithmes derrière une colonne password_algo, dispatchez la vérification vers la bonne bibliothèque, et à chaque vérification bcrypt réussie, re-hachez tout de suite avec Argon2id et mettez à jour la ligne. Les utilisateurs actifs migrent silencieusement dans leur cadence normale de connexion. Fixez une fenêtre de fin de vie de 6 à 12 mois pour les comptes inactifs, puis forcez une réinitialisation pour tout enregistrement encore sous bcrypt. Le même schéma fonctionne pour toute migration d’algorithme vers algorithme.
PBKDF2 reste-t-il un bon choix en 2026 ?
Uniquement quand la conformité FIPS-140 vous l’impose : typique dans le gouvernement fédéral, la santé régulée (HIPAA), et certains systèmes financiers. Utilisez HMAC-SHA-256 comme PRF avec au moins 600 000 itérations. PBKDF2 n’est pas mémoire-hard, donc il tombe face aux attaques GPU plus vite qu’Argon2id à budgets de latence équivalents. Si FIPS ne s’applique pas, prenez Argon2id et évitez la gymnastique de conformité.
La réponse 2026 sur le hachage de mots de passe tient en peu de lignes : Argon2id par défaut avec les paramètres OWASP, repli vers scrypt si Argon2 est indisponible, ne gardez bcrypt que là où le legacy l’exige, et réservez PBKDF2 aux systèmes liés à FIPS. Associez le hachage à un sel par enregistrement (toute bibliothèque moderne s’en charge automatiquement), à un pepper applicatif stocké hors de la base, et à une boucle de re-hachage pilotée par les connexions qui vous laisse pousser les facteurs de travail à mesure que le matériel progresse.
Générez un jeu de mots de passe représentatif avec le générateur de mot de passe aléatoire, benchmarkez votre chemin de vérification face à votre CPU de production, et inscrivez les paramètres dans un fichier de constantes pour que le prochain ingénieur sache exactement quoi pousser en 2028. Le contexte de sécurité complet (TLS, gestion des sessions, rate limiting, MFA) vit dans notre guide des bonnes pratiques de sécurité web. Choisissez le bon hachage aujourd’hui.