security
bcrypt : le choix sûr et ennuyeux
bcrypt : le choix sûr et ennuyeux
Une fonction de hachage de mot de passe conçue pour être lente, et pour le rester à mesure que le matériel progresse, via un facteur de coût réglable. J'utilise le paquet natif bcrypt plutôt que le bcryptjs en pur JS — plus rapide, mais il a besoin d'une compilation native, ce qui influe sur le choix de l'image de base Docker.
Les hachages rapides comme MD5 ou SHA-256 simple sont mauvais pour les mots de passe précisément parce qu'ils sont rapides — un vidage de hachages volé permet à un attaquant d'essayer des milliards de tentatives par seconde sur un GPU. Le facteur de coût de bcrypt rend chaque tentative délibérément coûteuse, transformant le brute-forcing d'un mot de passe fort de quelques heures en quelques siècles, sans ralentir de manière notable l'unique connexion légitime par utilisateur.
const hash = await bcrypt.hash(plainPassword, 12); // 12 = facteur de coût
Le coût est exponentiel — chaque +1 double à peu près le temps. 10–12 est le défaut moderne courant. Plus haut coûte plus de CPU par connexion, inscription ou réinitialisation, ce qui compte à l'échelle puisque chacune de ces opérations le paie. Ça vaut la peine de le re-benchmarker face au matériel actuel de temps en temps ; ce qui était assez lent il y a quelques années peut être cassable plus vite que prévu aujourd'hui.
Le salage est automatique — bcrypt intègre un sel aléatoire directement dans la chaîne de sortie, pas de colonne séparée, aucun risque d'oublier. En partie pourquoi il est resté le défaut raisonnable malgré son âge.
Argon2id est le choix plus moderne — dur en mémoire, coûteux à paralléliser sur GPU et ASIC, pas seulement lent en CPU. bcrypt a une longueur de mot de passe effective maximale d'environ 72 octets, ignorant silencieusement tout ce qui dépasse, et n'est pas dur en mémoire. Pour un projet neuf aujourd'hui, Argon2id est le meilleur choix. bcrypt reste correct — non cassé, largement audité — c'est juste l'option sûre et ennuyeuse plutôt que la plus forte disponible.
Une chose qui n'est pas optionnelle : ne compare jamais des hachages avec ===.
const isValid = await bcrypt.compare(plainPassword, storedHash);
Une comparaison naïve fuit de l'information de timing qui peut théoriquement servir à deviner le hachage octet par octet.