{"id":120,"date":"2026-06-13T16:39:54","date_gmt":"2026-06-13T16:39:54","guid":{"rendered":"https:\/\/shattered.io\/fr\/2026\/06\/13\/bcrypt-nodejs-hachage-mot-de-passe\/"},"modified":"2026-06-13T16:41:49","modified_gmt":"2026-06-13T16:41:49","slug":"bcrypt-nodejs-hachage-mot-de-passe","status":"publish","type":"post","link":"https:\/\/shattered.io\/fr\/2026\/06\/13\/bcrypt-nodejs-hachage-mot-de-passe\/","title":{"rendered":"bcrypt Node.js : hacher un mot de passe, 12 \u00e9tapes [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Une base de donn\u00e9es qui fuite ne devrait jamais livrer les mots de passe de vos utilisateurs en clair. C&#8217;est exactement le r\u00f4le de <strong>bcrypt<\/strong> : transformer un mot de passe en une empreinte lente \u00e0 calculer, sal\u00e9e et impossible \u00e0 inverser. En 2026, alors que les fuites de donn\u00e9es se multiplient en Europe, hacher correctement les mots de passe reste la premi\u00e8re ligne de d\u00e9fense c\u00f4t\u00e9 serveur. Ce tutoriel vous montre, en 12 \u00e9tapes concr\u00e8tes, comment int\u00e9grer <strong>bcrypt en Node.js<\/strong> pour inscrire, v\u00e9rifier et migrer des mots de passe dans une application Express r\u00e9elle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vous repartirez avec un projet complet et fonctionnel, du choix du <strong>cost factor<\/strong> jusqu&#8217;au rehachage automatique, en passant par les pi\u00e8ges classiques (limite des 72 octets, comparaison na\u00efve, blocage de l&#8217;event loop) et un guide de d\u00e9pannage couvrant les erreurs les plus fr\u00e9quentes. Tout le code est test\u00e9 sur Node.js 22 LTS et 24 LTS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pourquoi-hacher-un-mot-de-passe-avec-bcrypt-en-2026\">Pourquoi hacher un mot de passe avec bcrypt en 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un mot de passe ne doit jamais \u00eatre stock\u00e9 tel quel. Si votre base est compromise, des identifiants en clair donnent un acc\u00e8s imm\u00e9diat \u00e0 tous les comptes, et la plupart des utilisateurs r\u00e9emploient le m\u00eame mot de passe ailleurs. Le hachage casse cette cha\u00eene : \u00e0 la place du mot de passe, vous conservez une empreinte cryptographique dont on ne peut pas remonter au texte d&#8217;origine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tous les algorithmes de hachage ne se valent pas pour cette t\u00e2che. SHA-256 ou SHA-3 sont rapides par conception, ce qui les rend excellents pour v\u00e9rifier l&#8217;int\u00e9grit\u00e9 d&#8217;un fichier mais catastrophiques pour les mots de passe : un attaquant \u00e9quip\u00e9 d&#8217;une carte graphique teste des milliards de SHA-256 par seconde. bcrypt prend le contre-pied. Il a \u00e9t\u00e9 con\u00e7u en 1999 par Niels Provos et David Mazi\u00e8res pour \u00eatre <strong>volontairement lent<\/strong> et configurable, afin que le co\u00fbt d&#8217;une attaque par force brute reste prohibitif m\u00eame quand le mat\u00e9riel progresse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trois propri\u00e9t\u00e9s rendent bcrypt adapt\u00e9 au stockage de mots de passe. D&#8217;abord, il int\u00e8gre un <strong>sel unique<\/strong> dans chaque empreinte, ce qui neutralise les tables arc-en-ciel et emp\u00eache deux utilisateurs ayant le m\u00eame mot de passe d&#8217;obtenir le m\u00eame hash. Ensuite, son <strong>cost factor<\/strong> ajustable permet d&#8217;augmenter le temps de calcul d&#8217;ann\u00e9e en ann\u00e9e sans changer de code. Enfin, sa robustesse est \u00e9prouv\u00e9e depuis plus de vingt ans, et l&#8217;OWASP le classe toujours parmi les fonctions de hachage de mots de passe acceptables en 2026.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Concr\u00e8tement, bcrypt prot\u00e8ge trois moments cl\u00e9s du cycle de vie d&#8217;un compte. \u00c0 l&#8217;inscription, le mot de passe est hach\u00e9 avant d&#8217;atteindre la base. \u00c0 la connexion, le mot de passe saisi est compar\u00e9 au hash stock\u00e9 sans jamais d\u00e9chiffrer quoi que ce soit. Et lors d&#8217;une fuite, l&#8217;attaquant ne r\u00e9cup\u00e8re que des empreintes sal\u00e9es, dont le craquage prend des ann\u00e9es pour un mot de passe correct. C&#8217;est exactement ce que nous allons construire.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bcrypt-face-a-argon2-et-scrypt-que-choisir\">bcrypt face \u00e0 Argon2 et scrypt : que choisir<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Trois familles d&#8217;algorithmes dominent le hachage de mots de passe moderne : bcrypt, scrypt et Argon2. L&#8217;OWASP, dans son <em>Password Storage Cheat Sheet<\/em>, recommande <strong>Argon2id<\/strong> en premier choix lorsque vous le pouvez, puis scrypt et bcrypt comme alternatives parfaitement acceptables. bcrypt garde un avantage de taille : sa maturit\u00e9, sa disponibilit\u00e9 partout et l&#8217;absence de param\u00e8tres m\u00e9moire compliqu\u00e9s \u00e0 r\u00e9gler.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Crit\u00e8re<\/th><th>bcrypt<\/th><th>scrypt<\/th><th>Argon2id<\/th><\/tr><\/thead><tbody><tr><td>Ann\u00e9e de cr\u00e9ation<\/td><td>1999<\/td><td>2009<\/td><td>2015 (laur\u00e9at PHC)<\/td><\/tr><tr><td>Base algorithmique<\/td><td>Blowfish<\/td><td>PBKDF2 + m\u00e9moire<\/td><td>BLAKE2 + m\u00e9moire<\/td><\/tr><tr><td>R\u00e9sistance GPU\/ASIC<\/td><td>Bonne (CPU)<\/td><td>\u00c9lev\u00e9e (m\u00e9moire)<\/td><td>Tr\u00e8s \u00e9lev\u00e9e (m\u00e9moire + parall\u00e9lisme)<\/td><\/tr><tr><td>Param\u00e8tres \u00e0 r\u00e9gler<\/td><td>1 (cost)<\/td><td>3 (N, r, p)<\/td><td>3 (m\u00e9moire, temps, parall\u00e9lisme)<\/td><\/tr><tr><td>Sel int\u00e9gr\u00e9 au hash<\/td><td>Oui<\/td><td>Oui<\/td><td>Oui<\/td><\/tr><tr><td>Limite d&#8217;entr\u00e9e<\/td><td>72 octets<\/td><td>Aucune pratique<\/td><td>Aucune pratique<\/td><\/tr><tr><td>Recommandation OWASP 2026<\/td><td>Acceptable<\/td><td>Acceptable<\/td><td>Pr\u00e9f\u00e9r\u00e9<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Quand choisir bcrypt plut\u00f4t qu&#8217;Argon2 ? Quand vous voulez une solution simple, sans r\u00e9glage m\u00e9moire, compatible avec tout l&#8217;\u00e9cosyst\u00e8me Node.js et d\u00e9j\u00e0 pr\u00e9sente dans des millions de bases en production. Si vous d\u00e9marrez un projet neuf \u00e0 fortes exigences de s\u00e9curit\u00e9 et que vous ma\u00eetrisez le r\u00e9glage m\u00e9moire, Argon2id est le meilleur choix. Pour la grande majorit\u00e9 des applications web, bcrypt avec un cost factor bien calibr\u00e9 reste un choix s\u00fbr et d\u00e9fendable. Si vous h\u00e9sitez, lisez notre dossier d\u00e9di\u00e9 au <a href=\"\/fr\/securite-des-mots-de-passe\/\">stockage s\u00e9curis\u00e9 des mots de passe<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"comment-fonctionne-bcrypt-en-interne\">Comment fonctionne bcrypt en interne<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Comprendre le m\u00e9canisme \u00e9vite la plupart des erreurs d&#8217;impl\u00e9mentation. bcrypt repose sur trois piliers : l&#8217;algorithme Blowfish, un sel int\u00e9gr\u00e9 et un facteur de co\u00fbt exponentiel.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"le-moteur-blowfish-et-le-key-schedule-couteux\">Le moteur Blowfish et le key schedule co\u00fbteux<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">bcrypt d\u00e9rive du chiffrement Blowfish, mais d\u00e9tourne son initialisation de cl\u00e9. La fonction interne, baptis\u00e9e <em>EksBlowfishSetup<\/em> (Expensive key schedule), r\u00e9p\u00e8te l&#8217;initialisation des cl\u00e9s un grand nombre de fois. C&#8217;est cette r\u00e9p\u00e9tition qui rend le calcul lent et r\u00e9glable. L\u00e0 o\u00f9 SHA-256 produit une empreinte en quelques microsecondes, bcrypt vise d\u00e9lib\u00e9r\u00e9ment des centaines de millisecondes. Cette lenteur n&#8217;est pas un d\u00e9faut : c&#8217;est la fonctionnalit\u00e9 de s\u00e9curit\u00e9 elle-m\u00eame.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"le-sel-integre-genere-automatiquement\">Le sel int\u00e9gr\u00e9, g\u00e9n\u00e9r\u00e9 automatiquement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le sel est une valeur al\u00e9atoire de 16 octets (128 bits) ajout\u00e9e au mot de passe avant hachage. bcrypt le g\u00e9n\u00e8re pour vous et l&#8217;inscrit directement dans l&#8217;empreinte finale. Vous n&#8217;avez donc pas \u00e0 g\u00e9rer une colonne \u00ab sel \u00bb s\u00e9par\u00e9e dans votre base : le sel voyage avec le hash. Deux utilisateurs avec le mot de passe \u00ab motdepasse123 \u00bb obtiennent ainsi deux empreintes totalement diff\u00e9rentes, ce qui rend les tables pr\u00e9calcul\u00e9es inutiles.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"le-cost-factor-ou-nombre-de-tours\">Le cost factor, ou nombre de tours<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le <strong>cost factor<\/strong> (aussi appel\u00e9 <em>work factor<\/em> ou <em>salt rounds<\/em>) contr\u00f4le le nombre d&#8217;it\u00e9rations : le nombre de tours r\u00e9els vaut 2 puissance le cost. Un cost de 10 \u00e9quivaut \u00e0 1 024 it\u00e9rations, un cost de 12 \u00e0 4 096, un cost de 14 \u00e0 16 384. Chaque incr\u00e9ment <strong>double<\/strong> le temps de calcul. L&#8217;OWASP recommande un cost d&#8217;au moins 10, et la valeur de r\u00e9f\u00e9rence couramment retenue en 2025-2026 est <strong>12<\/strong>, \u00e0 calibrer pour viser entre 250 ms et 1 seconde par hachage sur votre mat\u00e9riel de production. Nous verrons \u00e0 l&#8217;\u00e9tape 9 comment mesurer et choisir cette valeur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"anatomie-dun-hash-bcrypt-decortiquer-le-format-2b\">Anatomie d&#8217;un hash bcrypt : d\u00e9cortiquer le format $2b$<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Une empreinte bcrypt n&#8217;est pas une cha\u00eene opaque : elle encode tout ce dont la v\u00e9rification a besoin. Prenons cet exemple r\u00e9el produit avec un cost de 12 :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$2b$12$Eq8X9q1mZ3oQpL5vK7nJ0u8Y2cF4dG6hR1tS3wV5xZ7aB9cD1eFq\n\n  |   |  |                      |\n  |   |  |                      +-- hash (31 caracteres Base64)\n  |   |  +-- sel (22 caracteres Base64, soit 16 octets)\n  |   +-- cost factor (ici 12, soit 4096 iterations)\n  +-- identifiant de version de l'algorithme ($2b$)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le tableau suivant d\u00e9taille chaque segment. C&#8217;est parce que le sel et le cost sont stock\u00e9s dans l&#8217;empreinte que <code>bcrypt.compare()<\/code> peut rev\u00e9rifier un mot de passe sans information suppl\u00e9mentaire.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Segment<\/th><th>Valeur exemple<\/th><th>Longueur<\/th><th>R\u00f4le<\/th><\/tr><\/thead><tbody><tr><td>Pr\u00e9fixe de version<\/td><td><code>$2b$<\/code><\/td><td>4 caract\u00e8res<\/td><td>Identifie la variante de l&#8217;algorithme<\/td><\/tr><tr><td>Cost factor<\/td><td><code>12<\/code><\/td><td>2 chiffres<\/td><td>Exposant du nombre d&#8217;it\u00e9rations (2^12)<\/td><\/tr><tr><td>Sel<\/td><td>22 caract\u00e8res<\/td><td>16 octets<\/td><td>Al\u00e9a unique encod\u00e9 en Base64<\/td><\/tr><tr><td>Empreinte<\/td><td>31 caract\u00e8res<\/td><td>23 octets<\/td><td>R\u00e9sultat final du hachage<\/td><\/tr><tr><td>Total<\/td><td>cha\u00eene compl\u00e8te<\/td><td>60 caract\u00e8res<\/td><td>Tient dans un VARCHAR(60) ou plus large<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Le pr\u00e9fixe m\u00e9rite une note. Les versions historiques <code>$2a$<\/code> et <code>$2y$<\/code> existent encore dans d&#8217;anciennes bases ; <code>$2b$<\/code> est la variante recommand\u00e9e aujourd&#8217;hui, qui corrige un bug de gestion de la longueur pr\u00e9sent dans certaines vieilles impl\u00e9mentations. La biblioth\u00e8que Node.js produit du <code>$2b$<\/code> par d\u00e9faut, mais sait v\u00e9rifier les anciens formats, ce qui facilite les migrations. Pour r\u00e9server la colonne en base, pr\u00e9voyez au moins 60 caract\u00e8res, voire 72 par s\u00e9curit\u00e9.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequis-versions-et-outils-necessaires\">Pr\u00e9requis : versions et outils n\u00e9cessaires<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ce tutoriel suppose des bases en JavaScript et en ligne de commande. Voici l&#8217;environnement exact utilis\u00e9. Les num\u00e9ros de version marqu\u00e9s \u00ab latest \u00bb signifient que vous devez installer la derni\u00e8re version stable au moment de la lecture.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Outil<\/th><th>Version recommand\u00e9e<\/th><th>V\u00e9rification<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22 LTS ou 24 LTS<\/td><td><code>node -v<\/code><\/td><\/tr><tr><td>npm<\/td><td>livr\u00e9 avec Node.js<\/td><td><code>npm -v<\/code><\/td><\/tr><tr><td>bcrypt (npm)<\/td><td>branche 5.x (latest)<\/td><td><code>npm ls bcrypt<\/code><\/td><\/tr><tr><td>Express<\/td><td>5.x (ou 4.x)<\/td><td><code>npm ls express<\/code><\/td><\/tr><tr><td>\u00c9diteur<\/td><td>VS Code (latest) ou \u00e9quivalent<\/td><td>&#8211;<\/td><\/tr><tr><td>OS<\/td><td>Linux, macOS ou Windows<\/td><td>&#8211;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">La biblioth\u00e8que <code>bcrypt<\/code> native compile un module C++ \u00e0 l&#8217;installation. Sur la plupart des syst\u00e8mes, npm t\u00e9l\u00e9charge un binaire pr\u00e9compil\u00e9 et tout fonctionne. Si la compilation \u00e9choue (Windows sans outils de build, conteneurs Alpine minimalistes), vous avez deux options : installer les outils de compilation, ou utiliser <code>bcryptjs<\/code>, une r\u00e9impl\u00e9mentation 100 % JavaScript sans d\u00e9pendance native. Nous y reviendrons en d\u00e9tail.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etapes-1-a-4-projet-dependances-et-premier-hash\">\u00c9tapes 1 \u00e0 4 : projet, d\u00e9pendances et premier hash<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-1-initialiser-le-projet-node-js\">\u00c9tape 1 : initialiser le projet Node.js<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez un dossier de travail et initialisez un projet. Nous activons les modules ES pour utiliser la syntaxe <code>import<\/code> moderne.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir bcrypt-demo && cd bcrypt-demo\nnpm init -y\nnpm pkg set type=\"module\"\nnode -v   # verifiez 22.x ou 24.x<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-2-installer-bcrypt\">\u00c9tape 2 : installer bcrypt<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Installez la biblioth\u00e8que native bcrypt. Express et la gestion de session viendront plus tard.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install bcrypt\nnpm ls bcrypt   # confirme la version installee<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si l&#8217;installation \u00e9choue avec une erreur <code>node-gyp<\/code>, passez directement \u00e0 la section sur <code>bcryptjs<\/code> ou consultez le d\u00e9pannage en fin d&#8217;article.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-3-generer-un-sel-explicitement\">\u00c9tape 3 : g\u00e9n\u00e9rer un sel explicitement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pour bien comprendre le m\u00e9canisme, g\u00e9n\u00e9rons d&#8217;abord un sel \u00e0 la main avant de le combiner au mot de passe. Cr\u00e9ez un fichier <code>hash.js<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hash.js\nimport bcrypt from \"bcrypt\";\n\nconst COST = 12;\nconst motDePasse = \"MonSuperMotDePasse!2026\";\n\nconst sel = await bcrypt.genSalt(COST);\nconsole.log(\"Sel genere :\", sel);\n\nconst empreinte = await bcrypt.hash(motDePasse, sel);\nconsole.log(\"Empreinte  :\", empreinte);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ex\u00e9cutez avec <code>node hash.js<\/code>. Sortie attendue (les valeurs varient \u00e0 chaque ex\u00e9cution) :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Sel genere : $2b$12$Eq8X9q1mZ3oQpL5vK7nJ0u\nEmpreinte  : $2b$12$Eq8X9q1mZ3oQpL5vK7nJ0u8Y2cF4dG6hR1tS3wV5xZ7aB9cD1eFq<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-4-hacher-en-une-seule-ligne\">\u00c9tape 4 : hacher en une seule ligne<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En production, vous n&#8217;avez pas besoin de g\u00e9n\u00e9rer le sel s\u00e9par\u00e9ment. Passez directement le cost \u00e0 <code>bcrypt.hash()<\/code> : la biblioth\u00e8que cr\u00e9e le sel pour vous. C&#8217;est l&#8217;approche recommand\u00e9e car elle r\u00e9duit les risques d&#8217;erreur.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hash-simple.js\nimport bcrypt from \"bcrypt\";\n\nconst COST = 12;\nconst empreinte = await bcrypt.hash(\"MonSuperMotDePasse!2026\", COST);\nconsole.log(empreinte);\n\/\/ $2b$12$... (60 caracteres, sel inclus)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notez l&#8217;usage de <code>await<\/code> : l&#8217;API asynchrone est obligatoire en production pour ne pas bloquer l&#8217;event loop. Nous y revenons \u00e0 l&#8217;\u00e9tape 12.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etapes-5-a-8-verification-express-et-stockage\">\u00c9tapes 5 \u00e0 8 : v\u00e9rification, Express et stockage<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-5-verifier-un-mot-de-passe-avec-bcrypt-compare\">\u00c9tape 5 : v\u00e9rifier un mot de passe avec bcrypt.compare<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pour v\u00e9rifier un mot de passe \u00e0 la connexion, n&#8217;essayez jamais de re-hacher puis comparer les cha\u00eenes vous-m\u00eame. Utilisez <code>bcrypt.compare()<\/code>, qui extrait le sel et le cost de l&#8217;empreinte stock\u00e9e, recalcule le hash et effectue une comparaison s\u00fbre.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ verify.js\nimport bcrypt from \"bcrypt\";\n\nconst empreinteStockee = await bcrypt.hash(\"secret123\", 12);\n\nconst ok = await bcrypt.compare(\"secret123\", empreinteStockee);\nconsole.log(\"Bon mot de passe :\", ok);       \/\/ true\n\nconst ko = await bcrypt.compare(\"mauvais\", empreinteStockee);\nconsole.log(\"Mauvais mot de passe :\", ko);    \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>bcrypt.compare()<\/code> renvoie un bool\u00e9en et g\u00e8re la comparaison en interne, ce qui r\u00e9duit l&#8217;exposition aux attaques par analyse de temps. Ne comparez jamais deux empreintes avec <code>===<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-6-construire-la-route-dinscription-express\">\u00c9tape 6 : construire la route d&#8217;inscription Express<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Installez Express, puis cr\u00e9ez un serveur avec une route d&#8217;inscription. Pour rester focalis\u00e9 sur bcrypt, nous simulons la base avec un simple tableau en m\u00e9moire.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install express<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport express from \"express\";\nimport bcrypt from \"bcrypt\";\n\nconst app = express();\napp.use(express.json());\n\nconst COST = 12;\nconst utilisateurs = []; \/\/ base simulee\n\napp.post(\"\/inscription\", async (req, res) => {\n  const { email, motDePasse } = req.body;\n  if (!email || !motDePasse) {\n    return res.status(400).json({ erreur: \"Champs manquants\" });\n  }\n  const empreinte = await bcrypt.hash(motDePasse, COST);\n  utilisateurs.push({ email, empreinte });\n  res.status(201).json({ message: \"Compte cree\" });\n});\n\napp.listen(3000, () => console.log(\"API sur http:\/\/localhost:3000\"));<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-7-ajouter-la-route-de-connexion\">\u00c9tape 7 : ajouter la route de connexion<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La connexion r\u00e9cup\u00e8re l&#8217;empreinte stock\u00e9e et la compare au mot de passe saisi. Renvoyez toujours le m\u00eame message d&#8217;erreur, que l&#8217;email soit inconnu ou le mot de passe faux, pour ne pas r\u00e9v\u00e9ler quels comptes existent.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ a ajouter dans server.js\napp.post(\"\/connexion\", async (req, res) => {\n  const { email, motDePasse } = req.body;\n  const user = utilisateurs.find((u) => u.email === email);\n\n  \/\/ message identique dans les deux cas d'echec\n  const echec = () => res.status(401).json({ erreur: \"Identifiants invalides\" });\n\n  if (!user) return echec();\n\n  const valide = await bcrypt.compare(motDePasse, user.empreinte);\n  if (!valide) return echec();\n\n  res.json({ message: \"Connexion reussie\" });\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-8-tester-lapi-avec-curl\">\u00c9tape 8 : tester l&#8217;API avec curl<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lancez le serveur avec <code>node server.js<\/code> et testez les deux routes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -X POST http:\/\/localhost:3000\/inscription \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"email\":\"alice@exemple.fr\",\"motDePasse\":\"Tr3sB0nMdp!\"}'\n# {\"message\":\"Compte cree\"}\n\ncurl -X POST http:\/\/localhost:3000\/connexion \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"email\":\"alice@exemple.fr\",\"motDePasse\":\"Tr3sB0nMdp!\"}'\n# {\"message\":\"Connexion reussie\"}\n\ncurl -X POST http:\/\/localhost:3000\/connexion \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"email\":\"alice@exemple.fr\",\"motDePasse\":\"faux\"}'\n# {\"erreur\":\"Identifiants invalides\"}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour ajouter ensuite des jetons de session, combinez bcrypt avec une couche d&#8217;authentification stateless comme dans notre tutoriel sur l&#8217;<a href=\"\/fr\/authentification-jwt-nodejs\/\">authentification JWT en Node.js<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etapes-9-a-12-cost-adaptatif-rehash-migration-et-performance\">\u00c9tapes 9 \u00e0 12 : cost adaptatif, rehash, migration et performance<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-9-mesurer-et-choisir-le-bon-cost-factor\">\u00c9tape 9 : mesurer et choisir le bon cost factor<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le bon cost d\u00e9pend de votre mat\u00e9riel. L&#8217;objectif est de viser environ 250 ms \u00e0 1 seconde par hachage. Mesurez sur votre serveur r\u00e9el avec ce banc d&#8217;essai :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ bench.js\nimport bcrypt from \"bcrypt\";\n\nfor (const cost of [10, 11, 12, 13, 14]) {\n  const debut = process.hrtime.bigint();\n  await bcrypt.hash(\"motDePasseDeTest\", cost);\n  const ms = Number(process.hrtime.bigint() - debut) \/ 1e6;\n  console.log(`cost ${cost} : ${ms.toFixed(0)} ms`);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Exemple de sortie sur une machine de d\u00e9veloppement r\u00e9cente :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cost 10 : 65 ms\ncost 11 : 130 ms\ncost 12 : 260 ms\ncost 13 : 520 ms\ncost 14 : 1040 ms<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ici, le cost 12 ou 13 atteint la cible. Choisissez la valeur la plus \u00e9lev\u00e9e que votre serveur tol\u00e8re sans d\u00e9grader l&#8217;exp\u00e9rience utilisateur \u00e0 la connexion. R\u00e9\u00e9valuez-la chaque ann\u00e9e : le mat\u00e9riel s&#8217;acc\u00e9l\u00e8re, le cost doit suivre.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-10-rehacher-quand-le-cost-a-change\">\u00c9tape 10 : rehacher quand le cost a chang\u00e9<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Quand vous augmentez le cost, les empreintes existantes restent au vieux co\u00fbt. Profitez de la connexion (le seul moment o\u00f9 vous avez le mot de passe en clair) pour rehacher silencieusement les comptes obsol\u00e8tes. Lisez le cost stock\u00e9 dans l&#8217;empreinte avec <code>bcrypt.getRounds()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ rehash a la connexion\nconst valide = await bcrypt.compare(motDePasse, user.empreinte);\nif (!valide) return echec();\n\nconst COST_CIBLE = 13;\nif (bcrypt.getRounds(user.empreinte) < COST_CIBLE) {\n  user.empreinte = await bcrypt.hash(motDePasse, COST_CIBLE);\n  \/\/ persister user.empreinte en base\n}\n\nres.json({ message: \"Connexion reussie\" });<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-11-migrer-depuis-sha-256-ou-md5\">\u00c9tape 11 : migrer depuis SHA-256 ou MD5<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Si vous reprenez une base o\u00f9 les mots de passe sont stock\u00e9s en SHA-256 ou MD5 non sal\u00e9, ne demandez pas \u00e0 tous vos utilisateurs de r\u00e9initialiser leur mot de passe d'un coup. La technique du <strong>double hachage<\/strong> permet une migration transparente : enveloppez l'ancien hash dans bcrypt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import bcrypt from \"bcrypt\";\nimport { createHash } from \"node:crypto\";\n\n\/\/ migration de masse : bcrypt(sha256(ancienMdp))\nfunction envelopper(ancienHashSha256) {\n  return bcrypt.hash(ancienHashSha256, 12);\n}\n\n\/\/ a la connexion d'un compte migre\nconst sha = createHash(\"sha256\").update(motDePasse).digest(\"hex\");\nconst valide = await bcrypt.compare(sha, user.empreinteEnveloppee);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Marquez ces comptes comme \u00ab migr\u00e9s \u00bb et, \u00e0 la premi\u00e8re connexion r\u00e9ussie, remplacez l'empreinte envelopp\u00e9e par un <code>bcrypt.hash(motDePasse, COST)<\/code> direct. La base se nettoie d'elle-m\u00eame au fil des connexions. Pour les fondamentaux du hachage, voyez notre guide sur les <a href=\"\/fr\/fonctions-de-hachage\/\">fonctions de hachage cryptographiques<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"etape-12-preserver-levent-loop-avec-lapi-asynchrone\">\u00c9tape 12 : pr\u00e9server l'event loop avec l'API asynchrone<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">bcrypt expose des fonctions synchrones (<code>hashSync<\/code>, <code>compareSync<\/code>) et asynchrones. En production, utilisez toujours les versions asynchrones : la variante native ex\u00e9cute le calcul lourd dans un thread du pool libuv, ce qui laisse votre serveur r\u00e9pondre \u00e0 d'autres requ\u00eates pendant le hachage.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ MAUVAIS : bloque l'event loop sur ~260 ms\nconst h = bcrypt.hashSync(motDePasse, 12);\n\n\/\/ BON : libere le thread principal pendant le calcul\nconst h = await bcrypt.hash(motDePasse, 12);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sous forte charge, le hachage synchrone fige le serveur : chaque connexion attend la fin du calcul pr\u00e9c\u00e9dent. L'API asynchrone, elle, parall\u00e9lise sur plusieurs threads et garde la latence stable. Le projet complet est d\u00e9sormais en place : initialisation, hachage, v\u00e9rification, rehash et migration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bcrypt-ou-bcryptjs-quelle-bibliotheque-choisir\">bcrypt ou bcryptjs : quelle biblioth\u00e8que choisir<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Deux paquets dominent l'\u00e9cosyst\u00e8me npm. <code>bcrypt<\/code> est une liaison native vers une impl\u00e9mentation C++, plus rapide. <code>bcryptjs<\/code> est une r\u00e9impl\u00e9mentation pure JavaScript, plus lente mais sans aucune d\u00e9pendance native, donc imm\u00e9diatement installable partout, y compris dans des environnements o\u00f9 la compilation est impossible (certains conteneurs, fonctions serverless, navigateur).<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Crit\u00e8re<\/th><th>bcrypt (natif)<\/th><th>bcryptjs (pur JS)<\/th><\/tr><\/thead><tbody><tr><td>Impl\u00e9mentation<\/td><td>C++ via node-gyp<\/td><td>JavaScript pur<\/td><\/tr><tr><td>Performance<\/td><td>Plus rapide<\/td><td>Plus lente (souvent 2 \u00e0 3x)<\/td><\/tr><tr><td>Installation<\/td><td>Compilation possible<\/td><td>Aucune compilation<\/td><\/tr><tr><td>Serverless \/ Alpine<\/td><td>Parfois probl\u00e9matique<\/td><td>Toujours compatible<\/td><\/tr><tr><td>Compatibilit\u00e9 des hash<\/td><td>Format $2b$ standard<\/td><td>Format $2b$ standard<\/td><\/tr><tr><td>API<\/td><td>hash, compare, genSalt<\/td><td>Identique<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Bonne nouvelle : les deux produisent des empreintes interop\u00e9rables. Un hash cr\u00e9\u00e9 par <code>bcrypt<\/code> se v\u00e9rifie avec <code>bcryptjs<\/code> et inversement. Vous pouvez donc d\u00e9velopper avec l'un et d\u00e9ployer avec l'autre sans casser vos comptes. R\u00e8gle simple : privil\u00e9giez <code>bcrypt<\/code> pour la performance sur un serveur classique, et basculez sur <code>bcryptjs<\/code> d\u00e8s que la compilation native pose probl\u00e8me.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"la-limite-des-72-octets-et-le-pre-hachage-sha-256\">La limite des 72 octets et le pr\u00e9-hachage SHA-256<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">bcrypt ignore tout ce qui d\u00e9passe <strong>72 octets<\/strong> dans le mot de passe d'entr\u00e9e. Pour de l'ASCII, cela fait 72 caract\u00e8res, mais un mot de passe riche en accents ou en \u00e9mojis atteint cette limite bien plus vite, car un caract\u00e8re UTF-8 peut occuper plusieurs octets. Au-del\u00e0, les octets suppl\u00e9mentaires sont silencieusement ignor\u00e9s. Deux mots de passe qui ne diff\u00e8rent qu'apr\u00e8s le 72e octet produisent alors le m\u00eame hash.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un probl\u00e8me connexe est la troncature au caract\u00e8re NUL dans d'anciennes impl\u00e9mentations. Pour les phrases de passe longues ou les jetons, la parade recommand\u00e9e par l'OWASP est de <strong>pr\u00e9-hacher en SHA-256<\/strong> puis d'encoder en Base64 avant de passer \u00e0 bcrypt. Cela ram\u00e8ne toute entr\u00e9e \u00e0 une longueur fixe et s\u00fbre.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import bcrypt from \"bcrypt\";\nimport { createHash } from \"node:crypto\";\n\nfunction preHacher(motDePasse) {\n  \/\/ SHA-256 -> Base64 : longueur fixe de 44 caracteres, < 72 octets\n  return createHash(\"sha256\").update(motDePasse, \"utf8\").digest(\"base64\");\n}\n\nconst empreinte = await bcrypt.hash(preHacher(motDePasseTresLong), 12);\nconst ok = await bcrypt.compare(preHacher(saisieUtilisateur), empreinte);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Appliquez le pr\u00e9-hachage des deux c\u00f4t\u00e9s, \u00e0 l'inscription comme \u00e0 la v\u00e9rification, sinon les empreintes ne correspondront pas. Si tous vos mots de passe restent courts et en ASCII, ce pr\u00e9-hachage est facultatif, mais il ne co\u00fbte rien et prot\u00e8ge l'avenir.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ajouter-un-pepper-un-secret-applicatif-en-plus-du-sel\">Ajouter un pepper : un secret applicatif en plus du sel<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le sel est stock\u00e9 avec l'empreinte ; le <strong>pepper<\/strong>, lui, est un secret unique \u00e0 votre application, conserv\u00e9 en dehors de la base de donn\u00e9es (variable d'environnement, gestionnaire de secrets, HSM). Si seule la base fuite, l'attaquant n'a pas le pepper et le crackage devient impraticable. L'OWASP recommande le pepper comme d\u00e9fense compl\u00e9mentaire, jamais en remplacement du sel.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import bcrypt from \"bcrypt\";\nimport { createHmac } from \"node:crypto\";\n\nconst PEPPER = process.env.PEPPER; \/\/ jamais en base, jamais dans le code\n\nfunction avecPepper(motDePasse) {\n  return createHmac(\"sha256\", PEPPER).update(motDePasse).digest(\"base64\");\n}\n\nconst empreinte = await bcrypt.hash(avecPepper(motDePasse), 12);\nconst ok = await bcrypt.compare(avecPepper(saisie), empreinte);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Combiner pepper et pr\u00e9-hachage via HMAC-SHA256 fait d'une pierre deux coups : vous contournez la limite des 72 octets et vous ajoutez le secret applicatif. Attention toutefois : changer de pepper invalide toutes les empreintes existantes. Pr\u00e9voyez une rotation g\u00e9r\u00e9e (versionnez le pepper, rehachez \u00e0 la connexion).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"stocker-lempreinte-en-base-postgresql-et-mongodb\">Stocker l'empreinte en base : PostgreSQL et MongoDB<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le tableau en m\u00e9moire des exemples pr\u00e9c\u00e9dents dispara\u00eet au red\u00e9marrage. En production, l'empreinte va dans une base de donn\u00e9es. Le point crucial reste le m\u00eame quelle que soit la base : r\u00e9servez une colonne assez large pour 60 caract\u00e8res, et ne stockez jamais le mot de passe en clair \u00e0 c\u00f4t\u00e9. Voici le sch\u00e9ma minimal c\u00f4t\u00e9 PostgreSQL.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- schema PostgreSQL\nCREATE TABLE utilisateurs (\n  id           SERIAL PRIMARY KEY,\n  email        VARCHAR(255) UNIQUE NOT NULL,\n  empreinte    VARCHAR(72) NOT NULL,   -- 60 suffit, 72 par securite\n  cree_le      TIMESTAMPTZ DEFAULT now()\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">C\u00f4t\u00e9 Node.js, l'inscription devient une insertion param\u00e9tr\u00e9e. Utilisez toujours des requ\u00eates param\u00e9tr\u00e9es pour \u00e9viter l'injection SQL, jamais de concat\u00e9nation de cha\u00eenes. L'empreinte produite par bcrypt ne contient que des caract\u00e8res s\u00fbrs, mais l'email, lui, vient de l'utilisateur.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ inscription avec pg\nimport bcrypt from \"bcrypt\";\nimport pg from \"pg\";\n\nconst pool = new pg.Pool();\nconst COST = 12;\n\nasync function inscrire(email, motDePasse) {\n  const empreinte = await bcrypt.hash(motDePasse, COST);\n  await pool.query(\n    \"INSERT INTO utilisateurs (email, empreinte) VALUES ($1, $2)\",\n    [email, empreinte]\n  );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Avec MongoDB et Mongoose, le r\u00e9flexe le plus propre consiste \u00e0 hacher dans un hook <code>pre(\"save\")<\/code>, de sorte que le mot de passe soit toujours hach\u00e9 avant toute \u00e9criture, m\u00eame si plusieurs routes cr\u00e9ent des comptes. Pensez \u00e0 ne hacher que si le champ a chang\u00e9, sinon une simple mise \u00e0 jour de profil re-hacherait inutilement l'empreinte.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ modele Mongoose\nimport mongoose from \"mongoose\";\nimport bcrypt from \"bcrypt\";\n\nconst schema = new mongoose.Schema({\n  email: { type: String, unique: true, required: true },\n  empreinte: { type: String, required: true },\n});\n\nschema.pre(\"save\", async function () {\n  if (!this.isModified(\"empreinte\")) return;\n  this.empreinte = await bcrypt.hash(this.empreinte, 12);\n});\n\nexport const Utilisateur = mongoose.model(\"Utilisateur\", schema);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dans les deux cas, n'exposez jamais le champ <code>empreinte<\/code> dans une r\u00e9ponse API. Avec Mongoose, marquez-le <code>select: false<\/code> ou retirez-le explicitement avant de s\u00e9rialiser l'objet utilisateur. Une empreinte n'est pas un secret aussi sensible qu'un mot de passe en clair, mais la divulguer ne sert qu'\u00e0 faciliter une attaque hors ligne.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ecrire-un-test-automatise-pour-le-hachage\">\u00c9crire un test automatis\u00e9 pour le hachage<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un test garantit que votre logique d'inscription et de connexion ne r\u00e9gresse pas. Node.js int\u00e8gre d\u00e9sormais un lanceur de tests natif, ce qui \u00e9vite toute d\u00e9pendance externe. Cr\u00e9ez un fichier <code>auth.test.js<\/code> et v\u00e9rifiez les trois invariants essentiels : une empreinte ne ressemble pas au mot de passe, un bon mot de passe valide, un mauvais \u00e9choue.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ auth.test.js\nimport { test } from \"node:test\";\nimport assert from \"node:assert\/strict\";\nimport bcrypt from \"bcrypt\";\n\ntest(\"l'empreinte differe du mot de passe\", async () => {\n  const mdp = \"MonMotDePasse!2026\";\n  const empreinte = await bcrypt.hash(mdp, 12);\n  assert.notEqual(empreinte, mdp);\n  assert.ok(empreinte.startsWith(\"$2b$12$\"));\n});\n\ntest(\"compare valide le bon mot de passe\", async () => {\n  const empreinte = await bcrypt.hash(\"secret\", 12);\n  assert.equal(await bcrypt.compare(\"secret\", empreinte), true);\n});\n\ntest(\"compare rejette le mauvais mot de passe\", async () => {\n  const empreinte = await bcrypt.hash(\"secret\", 12);\n  assert.equal(await bcrypt.compare(\"intrus\", empreinte), false);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lancez la suite avec <code>node --test<\/code>. Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --test\n# tests 3\n# pass 3\n# fail 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ces trois tests couvrent le c\u0153ur de votre s\u00e9curit\u00e9 d'authentification. Ajoutez-en un quatri\u00e8me pour le rehash et un cinqui\u00e8me pour le pr\u00e9-hachage si vous l'utilisez. Un cost r\u00e9duit (par exemple 8) acc\u00e9l\u00e8re les tests sans toucher au cost de production, externalis\u00e9 en variable d'environnement.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-pieges-courants-a-eviter-avec-bcrypt\">5 pi\u00e8ges courants \u00e0 \u00e9viter avec bcrypt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ces erreurs reviennent constamment dans les revues de code. Les conna\u00eetre \u00e0 l'avance vous \u00e9pargne des failles silencieuses.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Comparer avec <code>===<\/code> au lieu de <code>bcrypt.compare()<\/code>.<\/strong> Re-hacher puis comparer les cha\u00eenes ne fonctionne pas, car chaque hachage g\u00e9n\u00e8re un sel diff\u00e9rent. Utilisez toujours <code>compare()<\/code>.<\/li><li><strong>Utiliser un cost trop bas (8 ou moins).<\/strong> Le hachage devient quasi instantan\u00e9 pour l'attaquant. Visez au moins 10, id\u00e9alement 12, calibr\u00e9 sur votre mat\u00e9riel.<\/li><li><strong>Bloquer l'event loop avec <code>hashSync<\/code>.<\/strong> Sous charge, le serveur se fige. Utilisez les versions asynchrones avec <code>await<\/code>.<\/li><li><strong>Tronquer la colonne en base.<\/strong> Une empreinte bcrypt fait 60 caract\u00e8res ; un <code>VARCHAR(50)<\/code> coupe le hash et rend la v\u00e9rification impossible. Pr\u00e9voyez au moins 60, voire 72.<\/li><li><strong>Oublier la limite des 72 octets.<\/strong> Pour des phrases de passe longues ou non ASCII, pr\u00e9-hachez en SHA-256, sinon une partie du mot de passe est ignor\u00e9e.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"astuces-avancees-pour-la-production\">Astuces avanc\u00e9es pour la production<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Au-del\u00e0 du code de base, quelques pratiques distinguent une int\u00e9gration robuste d'un prototype. Premi\u00e8re astuce : imposez une <strong>limitation de d\u00e9bit<\/strong> sur la route de connexion. M\u00eame avec bcrypt, un attaquant qui peut tester sans limite finira par deviner les mots de passe faibles. Un middleware de rate limiting bloque les attaques par bourrage d'identifiants.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deuxi\u00e8me astuce : externalisez le cost dans une variable d'environnement (<code>process.env.BCRYPT_COST<\/code>) pour l'ajuster sans red\u00e9ployer. Troisi\u00e8me astuce : enregistrez la dur\u00e9e moyenne de hachage dans vos m\u00e9triques. Si elle chute sous 200 ms apr\u00e8s un changement de serveur, c'est le signal qu'il faut augmenter le cost. Quatri\u00e8me astuce : ne logguez jamais le mot de passe ni l'empreinte, m\u00eame en niveau debug ; un log compromis annule tous vos efforts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enfin, compl\u00e9tez bcrypt par une politique de mots de passe sens\u00e9e : longueur minimale de 12 caract\u00e8res, v\u00e9rification contre les listes de mots de passe compromis, et surtout proposez l'authentification \u00e0 deux facteurs. Le hachage prot\u00e8ge la base ; l'hygi\u00e8ne globale prot\u00e8ge les comptes. Pour aller plus loin sur l'authentification d\u00e9l\u00e9gu\u00e9e, comparez les approches dans notre guide <a href=\"\/fr\/oauth2-openid-connect-nodejs\/\">OAuth2 et OpenID Connect en Node.js<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"depannage-8-erreurs-frequentes-et-leurs-solutions\">D\u00e9pannage : 8 erreurs fr\u00e9quentes et leurs solutions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Voici les probl\u00e8mes que vous rencontrerez le plus souvent en int\u00e9grant bcrypt, avec leur cause et leur rem\u00e8de.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sympt\u00f4me<\/th><th>Cause probable<\/th><th>Solution<\/th><\/tr><\/thead><tbody><tr><td><code>node-gyp<\/code> \u00e9choue \u00e0 l'installation<\/td><td>Outils de compilation absents<\/td><td>Installer les build tools ou utiliser <code>bcryptjs<\/code><\/td><\/tr><tr><td><code>compare()<\/code> renvoie toujours false<\/td><td>Empreinte tronqu\u00e9e en base (VARCHAR trop court)<\/td><td>\u00c9largir la colonne \u00e0 60+ caract\u00e8res<\/td><\/tr><tr><td>Erreur \u00ab data and hash arguments required \u00bb<\/td><td>Empreinte <code>undefined<\/code> (mauvais champ)<\/td><td>V\u00e9rifier le nom de colonne et la requ\u00eate SQL<\/td><\/tr><tr><td>Mots de passe longs accept\u00e9s \u00e0 tort<\/td><td>Limite des 72 octets<\/td><td>Pr\u00e9-hacher en SHA-256 + Base64<\/td><\/tr><tr><td>Connexion tr\u00e8s lente sous charge<\/td><td>Usage de <code>hashSync<\/code>\/<code>compareSync<\/code><\/td><td>Passer aux versions asynchrones<\/td><\/tr><tr><td>Hash <code>$2a$<\/code> ne se v\u00e9rifie pas<\/td><td>Ancien format issu d'une autre lib<\/td><td>bcrypt v\u00e9rifie $2a$\/$2b$\/$2y$ : rev\u00e9rifier le champ<\/td><\/tr><tr><td>Cannot find module 'bcrypt'<\/td><td>Module non install\u00e9 ou mauvais runtime<\/td><td><code>npm install bcrypt<\/code> et v\u00e9rifier <code>type:module<\/code><\/td><\/tr><tr><td>Hash diff\u00e9rent \u00e0 chaque ex\u00e9cution<\/td><td>Comportement normal (sel al\u00e9atoire)<\/td><td>Ne pas comparer les hash entre eux, utiliser <code>compare()<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Le huiti\u00e8me cas m\u00e9rite insistance car il d\u00e9route les d\u00e9butants : oui, hacher deux fois le m\u00eame mot de passe donne deux empreintes diff\u00e9rentes. C'est voulu, gr\u00e2ce au sel al\u00e9atoire. La seule fa\u00e7on correcte de v\u00e9rifier reste <code>bcrypt.compare()<\/code>, qui sait extraire le sel de l'empreinte stock\u00e9e.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"related-coverage\">Related Coverage<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/fr\/securite-des-mots-de-passe\/\">S\u00e9curit\u00e9 des mots de passe : longueur, hachage et gestionnaires<\/a><\/li><li><a href=\"\/fr\/authentification-jwt-nodejs\/\">Authentification JWT en Node.js : 12 \u00e9tapes<\/a><\/li><li><a href=\"\/fr\/oauth2-openid-connect-nodejs\/\">OAuth2 en Node.js : flux s\u00e9curis\u00e9 en 12 \u00e9tapes<\/a><\/li><li><a href=\"\/fr\/fonctions-de-hachage\/\">Les fonctions de hachage cryptographiques expliqu\u00e9es<\/a><\/li><li><a href=\"\/fr\/comparatif-gestionnaire-mots-de-passe-2026\/\">Comparatif des gestionnaires de mots de passe 2026<\/a><\/li><li><a href=\"\/fr\/sha-256\/\">SHA-256 expliqu\u00e9 : l'empreinte de 256 bits<\/a><\/li><li><a href=\"\/fr\/security\/\">Dossier : s\u00e9curit\u00e9 en ligne<\/a><\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"foire-aux-questions-sur-bcrypt-en-node-js\">Foire aux questions sur bcrypt en Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quel-cost-factor-bcrypt-utiliser-en-2026\">Quel cost factor bcrypt utiliser en 2026 ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Visez un cost d'au moins 10, et 12 comme valeur de r\u00e9f\u00e9rence. Le crit\u00e8re r\u00e9el n'est pas le chiffre mais le temps : calibrez le cost pour que chaque hachage prenne entre 250 ms et 1 seconde sur votre serveur de production. Mesurez avec le banc d'essai de l'\u00e9tape 9 et r\u00e9\u00e9valuez chaque ann\u00e9e.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"bcrypt-ou-argon2-lequel-est-le-plus-sur\">bcrypt ou Argon2, lequel est le plus s\u00fbr ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">L'OWASP recommande Argon2id en premier choix pour les nouveaux projets, car sa r\u00e9sistance m\u00e9moire complique les attaques par GPU et ASIC. bcrypt reste n\u00e9anmoins parfaitement acceptable, plus simple \u00e0 r\u00e9gler et \u00e9prouv\u00e9 depuis plus de vingt ans. Pour la plupart des applications web, bcrypt bien configur\u00e9 suffit.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"faut-il-stocker-le-sel-separement-avec-bcrypt\">Faut-il stocker le sel s\u00e9par\u00e9ment avec bcrypt ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non. bcrypt int\u00e8gre le sel directement dans l'empreinte de 60 caract\u00e8res. Vous n'avez besoin que d'une seule colonne pour stocker le hash. <code>bcrypt.compare()<\/code> extrait automatiquement le sel et le cost de cette cha\u00eene pour v\u00e9rifier le mot de passe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pourquoi-bcrypt-ignore-t-il-les-mots-de-passe-de-plus-de-72-octets\">Pourquoi bcrypt ignore-t-il les mots de passe de plus de 72 octets ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">C'est une limite de l'algorithme issu de Blowfish. Au-del\u00e0 de 72 octets, les caract\u00e8res suppl\u00e9mentaires ne sont pas pris en compte. Pour g\u00e9rer des phrases de passe longues ou riches en caract\u00e8res accentu\u00e9s, pr\u00e9-hachez le mot de passe en SHA-256 puis encodez-le en Base64 avant de le passer \u00e0 bcrypt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"dois-je-utiliser-bcrypt-ou-bcryptjs\">Dois-je utiliser bcrypt ou bcryptjs ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Utilisez <code>bcrypt<\/code> (natif) pour la performance sur un serveur classique. Basculez sur <code>bcryptjs<\/code> (pur JavaScript) si la compilation native \u00e9choue, par exemple en serverless ou sur des images Alpine minimalistes. Les deux produisent des empreintes interop\u00e9rables au format $2b$.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"comment-migrer-mes-mots-de-passe-md5-ou-sha-256-vers-bcrypt\">Comment migrer mes mots de passe MD5 ou SHA-256 vers bcrypt ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Enveloppez les anciens hash dans bcrypt sans demander \u00e0 vos utilisateurs de r\u00e9initialiser leur mot de passe. \u00c0 chaque connexion r\u00e9ussie d'un compte migr\u00e9, remplacez l'empreinte envelopp\u00e9e par un hash bcrypt direct du mot de passe. La base se met \u00e0 jour progressivement, comme d\u00e9taill\u00e9 \u00e0 l'\u00e9tape 11.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"bcrypt-est-il-resistant-a-linformatique-quantique\">bcrypt est-il r\u00e9sistant \u00e0 l'informatique quantique ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le hachage de mots de passe n'est pas menac\u00e9 de la m\u00eame fa\u00e7on que le chiffrement asym\u00e9trique par les ordinateurs quantiques. L'algorithme de Grover offrirait au mieux une acc\u00e9l\u00e9ration quadratique des recherches, que l'on compense en augmentant l\u00e9g\u00e8rement le cost. bcrypt avec un cost ad\u00e9quat reste pertinent face \u00e0 cette menace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Sources et r\u00e9f\u00e9rences : <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Password_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noreferrer noopener\">OWASP Password Storage Cheat Sheet<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Bcrypt\" target=\"_blank\" rel=\"noreferrer noopener\">bcrypt (Wikipedia)<\/a>, <a href=\"https:\/\/github.com\/kelektiv\/node.bcrypt.js\" target=\"_blank\" rel=\"noreferrer noopener\">node.bcrypt.js<\/a>, <a href=\"https:\/\/nodejs.org\/en\/about\/previous-releases\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js Releases<\/a>, <a href=\"https:\/\/expressjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Express<\/a>. Article publi\u00e9 le 13 juin 2026.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Une base de donn\u00e9es qui fuite ne devrait jamais livrer les mots de passe de vos utilisateurs en clair. C&#8217;est exactement le r\u00f4le de bcrypt : transformer un mot de\u2026<\/p>\n","protected":false},"author":5,"featured_media":121,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,3],"tags":[],"class_list":["post-120","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/120","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/comments?post=120"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/120\/revisions"}],"predecessor-version":[{"id":122,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/120\/revisions\/122"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media\/121"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media?parent=120"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/categories?post=120"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/tags?post=120"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}