{"id":153,"date":"2026-06-14T20:23:29","date_gmt":"2026-06-14T20:23:29","guid":{"rendered":"https:\/\/shattered.io\/fr\/2026\/06\/14\/hmac-sha256-nodejs\/"},"modified":"2026-06-14T20:24:51","modified_gmt":"2026-06-14T20:24:51","slug":"hmac-sha256-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fr\/2026\/06\/14\/hmac-sha256-nodejs\/","title":{"rendered":"HMAC-SHA256 en Node.js : signer une API en 12 \u00e9tapes [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">La signature de messages avec <strong>HMAC-SHA256<\/strong> prot\u00e8ge vos API, vos webhooks et vos tokens contre la falsification. Contrairement \u00e0 un simple hachage SHA-256, HMAC combine une cl\u00e9 secr\u00e8te au message, ce qui bloque les attaques par extension de longueur et garantit qu&#8217;aucun tiers ne peut forger une requ\u00eate valide. Ce tutoriel construit, \u00e9tape par \u00e9tape, un projet Node.js complet : g\u00e9n\u00e9ration de cl\u00e9, fonction de signature, v\u00e9rification en temps constant, middleware Express et v\u00e9rification de webhook de type Stripe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tout repose sur le module natif <code>node:crypto<\/code>. Aucune d\u00e9pendance cryptographique externe n&#8217;est n\u00e9cessaire. \u00c0 la fin, vous disposerez d&#8217;une API qui signe ses r\u00e9ponses, v\u00e9rifie l&#8217;authenticit\u00e9 des requ\u00eates entrantes et rejette les rejeux (replay attacks). Comptez 30 minutes pour suivre les 12 \u00e9tapes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"hmac-en-bref-signature-integrite-et-authentification\">HMAC en bref : signature, int\u00e9grit\u00e9 et authentification<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC signifie <em>Hash-based Message Authentication Code<\/em>. Le m\u00e9canisme est d\u00e9fini par la <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc2104\" target=\"_blank\" rel=\"noopener\">RFC 2104<\/a> et normalis\u00e9 par le <a href=\"https:\/\/csrc.nist.gov\/pubs\/fips\/198-1\/final\" target=\"_blank\" rel=\"noopener\">FIPS 198-1<\/a> du NIST. Il prend deux entr\u00e9es, une cl\u00e9 secr\u00e8te et un message, puis produit une empreinte de taille fixe. Avec SHA-256, cette empreinte fait 256 bits, soit 64 caract\u00e8res en hexad\u00e9cimal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le point cl\u00e9 : seul celui qui d\u00e9tient la cl\u00e9 secr\u00e8te peut produire une signature valide. Le destinataire recalcule la signature de son c\u00f4t\u00e9 et compare. Si les deux correspondent, le message est authentique et n&#8217;a pas \u00e9t\u00e9 modifi\u00e9. C&#8217;est pr\u00e9cis\u00e9ment ce que font Stripe, GitHub et Slack pour signer leurs webhooks.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pourquoi-pas-un-simple-sha-256\">Pourquoi pas un simple SHA-256 ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Un hachage brut <code>SHA256(message)<\/code> n&#8217;utilise aucune cl\u00e9. N&#8217;importe qui peut le recalculer, donc il n&#8217;authentifie rien. Pire, les fonctions de la famille Merkle-Damg\u00e5rd (SHA-1, SHA-256, SHA-512) sont vuln\u00e9rables aux attaques par extension de longueur. Si vous signez na\u00efvement avec <code>SHA256(secret + message)<\/code>, un attaquant qui conna\u00eet la longueur du secret peut ajouter des donn\u00e9es au message et calculer une signature valide sans jamais conna\u00eetre le secret. HMAC \u00e9limine ce risque par sa construction interne \u00e0 double hachage, avec des remplissages interne et externe distincts.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hmac-face-aux-autres-mecanismes\">HMAC face aux autres m\u00e9canismes<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>M\u00e9canisme<\/th><th>Cl\u00e9<\/th><th>Usage principal<\/th><th>Vitesse<\/th><th>R\u00e9sistance length-extension<\/th><\/tr><\/thead><tbody><tr><td>SHA-256 brut<\/td><td>Non<\/td><td>Int\u00e9grit\u00e9 simple<\/td><td>Tr\u00e8s rapide<\/td><td>Non<\/td><\/tr><tr><td>HMAC-SHA256<\/td><td>Sym\u00e9trique<\/td><td>Authentification de message, API, webhooks<\/td><td>Rapide<\/td><td>Oui<\/td><\/tr><tr><td>bcrypt \/ Argon2<\/td><td>Non (sel interne)<\/td><td>Stockage de mots de passe<\/td><td>Lent (volontaire)<\/td><td>Sans objet<\/td><\/tr><tr><td>RSA \/ ECDSA<\/td><td>Asym\u00e9trique<\/td><td>Signature publique v\u00e9rifiable<\/td><td>Lent<\/td><td>Sans objet<\/td><\/tr><tr><td>JWT HS256<\/td><td>Sym\u00e9trique (HMAC)<\/td><td>Tokens de session<\/td><td>Rapide<\/td><td>Oui<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC est sym\u00e9trique : l&#8217;\u00e9metteur et le r\u00e9cepteur partagent la m\u00eame cl\u00e9. Si vous avez besoin qu&#8217;un tiers v\u00e9rifie une signature sans pouvoir en cr\u00e9er, utilisez une signature asym\u00e9trique (RSA ou ECDSA). Pour le stockage de mots de passe, HMAC n&#8217;est pas adapt\u00e9 : pr\u00e9f\u00e9rez <a href=\"\/fr\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt ou Argon2<\/a>, con\u00e7us pour \u00eatre lents \u00e0 dessein.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequis-versions-et-outils\">Pr\u00e9requis : versions et outils<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le projet n&#8217;utilise que des modules natifs, plus Express pour la d\u00e9monstration d&#8217;API. Voici l&#8217;environnement exact test\u00e9 pour ce tutoriel.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Composant<\/th><th>Version<\/th><th>R\u00f4le<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>22 LTS (\u00ab Jod \u00bb)<\/td><td>Runtime, fournit <code>node:crypto<\/code><\/td><\/tr><tr><td>npm<\/td><td>10.x (livr\u00e9 avec Node 22)<\/td><td>Gestion des paquets<\/td><\/tr><tr><td>Express<\/td><td>5.x<\/td><td>Serveur HTTP de d\u00e9monstration<\/td><\/tr><tr><td>node:crypto<\/td><td>Natif<\/td><td>HMAC, g\u00e9n\u00e9ration de cl\u00e9, comparaison s\u00fbre<\/td><\/tr><tr><td>node:test<\/td><td>Natif<\/td><td>Tests unitaires int\u00e9gr\u00e9s<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">V\u00e9rifiez votre version de Node avant de commencer. HMAC fonctionne depuis longtemps dans Node, mais <code>node:test<\/code> et Express 5 supposent une version r\u00e9cente.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --version\nv22.14.0\n$ npm --version\n10.9.2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si <code>node --version<\/code> renvoie une version inf\u00e9rieure \u00e0 20, installez Node 22 LTS depuis <a href=\"https:\/\/nodejs.org\/api\/crypto.html\" target=\"_blank\" rel=\"noopener\">nodejs.org<\/a> ou via un gestionnaire de versions comme nvm. Une connaissance de base de JavaScript et de la ligne de commande suffit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-1-initialiser-le-projet-node-js\">\u00c9tape 1 : initialiser le projet Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez un dossier et initialisez un projet en modules ES. Le drapeau <code>\"type\": \"module\"<\/code> active la syntaxe <code>import<\/code> moderne, recommand\u00e9e en 2026.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir hmac-api &amp;&amp; cd hmac-api\n$ npm init -y\n$ npm pkg set type=module\n$ npm install express@5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Votre <code>package.json<\/code> doit maintenant contenir <code>\"type\": \"module\"<\/code>. Express est la seule d\u00e9pendance ; toute la cryptographie vient du module natif <code>node:crypto<\/code>, sans paquet tiers \u00e0 auditer ou \u00e0 maintenir.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-2-generer-une-cle-secrete-hmac-robuste\">\u00c9tape 2 : g\u00e9n\u00e9rer une cl\u00e9 secr\u00e8te HMAC robuste<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La s\u00e9curit\u00e9 d&#8217;HMAC repose enti\u00e8rement sur la qualit\u00e9 de la cl\u00e9. Le guide des <a href=\"https:\/\/cyber.gouv.fr\/publications\/mecanismes-cryptographiques\" target=\"_blank\" rel=\"noopener\">m\u00e9canismes cryptographiques de l&#8217;ANSSI<\/a> recommande au minimum 128 bits pour une cl\u00e9 sym\u00e9trique ; 256 bits constituent un choix prudent et standard. N&#8217;utilisez jamais une cha\u00eene choisie \u00e0 la main comme <code>\"motdepasse123\"<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez un fichier <code>keygen.js<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ keygen.js\nimport { randomBytes } from 'node:crypto';\n\n\/\/ 32 octets = 256 bits, encod\u00e9s en hexad\u00e9cimal (64 caract\u00e8res)\nconst secret = randomBytes(32).toString('hex');\nconsole.log('HMAC_SECRET=' + secret);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ex\u00e9cutez-le et copiez la cl\u00e9 g\u00e9n\u00e9r\u00e9e :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node keygen.js\nHMAC_SECRET=9f8c2b1e4a7d6053c8f1b29e7a4d3c6b0e5f8a1d2c4b6e9f0a3d5c7b8e1f4a2d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>randomBytes<\/code> est un g\u00e9n\u00e9rateur cryptographiquement s\u00fbr (CSPRNG). N&#8217;utilisez jamais <code>Math.random()<\/code>, qui est pr\u00e9visible. Stockez la cl\u00e9 dans une variable d&#8217;environnement, jamais dans le code source.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-3-charger-la-cle-depuis-lenvironnement\">\u00c9tape 3 : charger la cl\u00e9 depuis l&#8217;environnement<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9ez un fichier <code>.env<\/code> avec votre cl\u00e9, et ajoutez-le imm\u00e9diatement \u00e0 <code>.gitignore<\/code> pour ne jamais le committer.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># .env\nHMAC_SECRET=9f8c2b1e4a7d6053c8f1b29e7a4d3c6b0e5f8a1d2c4b6e9f0a3d5c7b8e1f4a2d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Node 22 lit nativement les fichiers <code>.env<\/code> via le drapeau <code>--env-file<\/code>, sans le paquet <code>dotenv<\/code>. Cr\u00e9ez <code>config.js<\/code> qui valide la pr\u00e9sence de la cl\u00e9 au d\u00e9marrage :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ config.js\nconst secret = process.env.HMAC_SECRET;\n\nif (!secret || secret.length &lt; 32) {\n  throw new Error('HMAC_SECRET manquant ou trop court (minimum 32 caract\u00e8res).');\n}\n\nexport const HMAC_SECRET = secret;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Charger la cl\u00e9 une seule fois au d\u00e9marrage, et non \u00e0 chaque requ\u00eate, \u00e9vite des lectures inutiles et fait \u00e9chouer l&#8217;application t\u00f4t si la configuration est absente. C&#8217;est une pratique recommand\u00e9e par l&#8217;<a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cryptographic_Storage_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener\">OWASP Cryptographic Storage Cheat Sheet<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-4-ecrire-la-fonction-de-signature-hmac-sha256\">\u00c9tape 4 : \u00e9crire la fonction de signature HMAC-SHA256<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Voici le c\u0153ur du projet. Cr\u00e9ez <code>hmac.js<\/code> avec une fonction de signature simple et r\u00e9utilisable.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.js\nimport { createHmac } from 'node:crypto';\nimport { HMAC_SECRET } from '.\/config.js';\n\nexport function sign(message) {\n  return createHmac('sha256', HMAC_SECRET)\n    .update(message, 'utf8')\n    .digest('hex');\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Testez imm\u00e9diatement dans un fichier <code>demo.js<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ demo.js\nimport { sign } from '.\/hmac.js';\n\nconst message = 'commande:1042;montant:49.90';\nconsole.log('Signature :', sign(message));<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --env-file=.env demo.js\nSignature : 7b1d4f8e2a9c0b3d6e5f8a1c4d7b0e3f6a9c2d5e8b1f4a7c0d3e6b9f2a5c8d1e<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La m\u00eame cl\u00e9 et le m\u00eame message produisent toujours la m\u00eame empreinte de 64 caract\u00e8res hexad\u00e9cimaux. Changez un seul caract\u00e8re du message et toute la signature change radicalement, c&#8217;est l&#8217;effet d&#8217;avalanche. Cette propri\u00e9t\u00e9 est la base de la d\u00e9tection de modification.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-5-verifier-une-signature-en-temps-constant\">\u00c9tape 5 : v\u00e9rifier une signature en temps constant<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;erreur la plus r\u00e9pandue consiste \u00e0 comparer deux signatures avec <code>===<\/code>. Cette comparaison s&#8217;arr\u00eate au premier caract\u00e8re diff\u00e9rent, ce qui ouvre la porte \u00e0 une <strong>attaque par chronom\u00e9trage<\/strong> (timing attack) : un attaquant mesure le temps de r\u00e9ponse pour deviner la signature octet par octet. Utilisez <code>crypto.timingSafeEqual<\/code>, qui compare en temps constant.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.js (ajout)\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function verify(message, receivedSignature) {\n  const expected = sign(message);\n  const a = Buffer.from(expected, 'hex');\n  const b = Buffer.from(receivedSignature, 'hex');\n\n  \/\/ timingSafeEqual exige des buffers de m\u00eame longueur\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La v\u00e9rification de longueur avant <code>timingSafeEqual<\/code> est n\u00e9cessaire, car la fonction l\u00e8ve une exception si les deux buffers diff\u00e8rent en taille. Comme la longueur d&#8217;une empreinte HMAC-SHA256 est toujours de 32 octets, cette fuite de longueur n&#8217;a aucune valeur exploitable pour un attaquant.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ v\u00e9rification en pratique\nimport { sign, verify } from '.\/hmac.js';\n\nconst msg = 'commande:1042;montant:49.90';\nconst sig = sign(msg);\n\nconsole.log(verify(msg, sig));                 \/\/ true\nconsole.log(verify('message falsifi\u00e9', sig));  \/\/ false<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-6-ajouter-un-horodatage-contre-les-rejeux\">\u00c9tape 6 : ajouter un horodatage contre les rejeux<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Une signature valide reste valide ind\u00e9finiment. Un attaquant qui intercepte une requ\u00eate sign\u00e9e peut la rejouer plus tard. La parade : inclure un horodatage dans le message sign\u00e9 et rejeter les requ\u00eates trop anciennes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.js (ajout)\nconst TOLERANCE_MS = 5 * 60 * 1000; \/\/ 5 minutes\n\nexport function signWithTimestamp(body) {\n  const timestamp = Date.now().toString();\n  const signature = sign(timestamp + '.' + body);\n  return { timestamp, signature };\n}\n\nexport function verifyWithTimestamp(body, timestamp, signature) {\n  const age = Math.abs(Date.now() - Number(timestamp));\n  if (Number.isNaN(age) || age &gt; TOLERANCE_MS) return false;\n  return verify(timestamp + '.' + body, signature);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En liant l&#8217;horodatage \u00e0 la signature, vous emp\u00eachez aussi un attaquant de modifier l&#8217;heure sans invalider la signature. Une fen\u00eatre de 5 minutes est le compromis retenu par Stripe : elle absorbe les d\u00e9calages d&#8217;horloge entre serveurs tout en limitant la fen\u00eatre de rejeu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-7-creer-un-middleware-express-de-verification\">\u00c9tape 7 : cr\u00e9er un middleware Express de v\u00e9rification<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Passons \u00e0 une API r\u00e9elle. Le middleware lit la signature et l&#8217;horodatage dans les en-t\u00eates, puis valide le corps brut de la requ\u00eate. Point crucial : la signature doit porter sur le corps <em>brut<\/em>, avant tout parsing JSON, car la s\u00e9rialisation peut r\u00e9ordonner les cl\u00e9s.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nimport express from 'express';\nimport { verifyWithTimestamp } from '.\/hmac.js';\n\nconst app = express();\n\n\/\/ Capture le corps brut pour la v\u00e9rification HMAC\napp.use(express.json({\n  verify: (req, _res, buf) =&gt; { req.rawBody = buf.toString('utf8'); }\n}));\n\nfunction hmacAuth(req, res, next) {\n  const signature = req.get('x-signature');\n  const timestamp = req.get('x-timestamp');\n\n  if (!signature || !timestamp) {\n    return res.status(401).json({ error: 'En-t\u00eates de signature manquants.' });\n  }\n  if (!verifyWithTimestamp(req.rawBody, timestamp, signature)) {\n    return res.status(401).json({ error: 'Signature invalide ou expir\u00e9e.' });\n  }\n  next();\n}\n\napp.post('\/api\/commande', hmacAuth, (req, res) =&gt; {\n  res.json({ statut: 're\u00e7u', commande: req.body });\n});\n\napp.listen(3000, () =&gt; console.log('API HMAC sur http:\/\/localhost:3000'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cette approche reprend le principe utilis\u00e9 pour <a href=\"\/fr\/authentification-jwt-nodejs\/\">l&#8217;authentification JWT en Node.js<\/a>, mais appliqu\u00e9e \u00e0 des requ\u00eates machine \u00e0 machine plut\u00f4t qu&#8217;\u00e0 des sessions utilisateur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-8-ecrire-un-client-qui-signe-ses-requetes\">\u00c9tape 8 : \u00e9crire un client qui signe ses requ\u00eates<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le client calcule la signature avec la cl\u00e9 partag\u00e9e et l&#8217;envoie dans les en-t\u00eates. Voici un client utilisant <code>fetch<\/code>, natif dans Node 22.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ client.js\nimport { signWithTimestamp } from '.\/hmac.js';\n\nconst body = JSON.stringify({ produit: 'cl\u00e9 U2F', quantite: 2 });\nconst { timestamp, signature } = signWithTimestamp(body);\n\nconst res = await fetch('http:\/\/localhost:3000\/api\/commande', {\n  method: 'POST',\n  headers: {\n    'content-type': 'application\/json',\n    'x-timestamp': timestamp,\n    'x-signature': signature\n  },\n  body\n});\n\nconsole.log(res.status, await res.json());<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">D\u00e9marrez le serveur dans un terminal, puis lancez le client dans un autre :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --env-file=.env server.js\nAPI HMAC sur http:\/\/localhost:3000\n\n# autre terminal\n$ node --env-file=.env client.js\n200 { statut: 're\u00e7u', commande: { produit: 'cl\u00e9 U2F', quantite: 2 } }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Modifiez un seul octet du corps c\u00f4t\u00e9 client apr\u00e8s la signature, et le serveur renverra <code>401 Signature invalide<\/code>. C&#8217;est exactement le comportement attendu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-9-verifier-un-webhook-de-type-stripe\">\u00c9tape 9 : v\u00e9rifier un webhook de type Stripe<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Les fournisseurs SaaS signent leurs webhooks avec HMAC-SHA256. Le format d&#8217;en-t\u00eate de Stripe ressemble \u00e0 <code>t=1718000000,v1=&lt;signature&gt;<\/code>. Voici comment le v\u00e9rifier correctement.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ webhook.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function verifyStripeSignature(rawBody, header, endpointSecret) {\n  const parts = Object.fromEntries(\n    header.split(',').map(p =&gt; p.split('='))\n  );\n  const timestamp = parts.t;\n  const received = parts.v1;\n  if (!timestamp || !received) return false;\n\n  const signedPayload = `${timestamp}.${rawBody}`;\n  const expected = createHmac('sha256', endpointSecret)\n    .update(signedPayload, 'utf8')\n    .digest('hex');\n\n  const a = Buffer.from(expected, 'hex');\n  const b = Buffer.from(received, 'hex');\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notez que Stripe signe la concat\u00e9nation <code>timestamp.payload<\/code>, exactement le sch\u00e9ma anti-rejeu de l&#8217;\u00e9tape 6. R\u00e9utiliser ce motif maison vous pr\u00e9pare \u00e0 int\u00e9grer n&#8217;importe quel webhook professionnel sans surprise.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-10-ecrire-des-tests-unitaires-avec-nodetest\">\u00c9tape 10 : \u00e9crire des tests unitaires avec node:test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Node 22 int\u00e8gre un lanceur de tests natif. Aucune d\u00e9pendance comme Jest ou Mocha n&#8217;est requise. Cr\u00e9ez <code>hmac.test.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.test.js\nimport { test } from 'node:test';\nimport assert from 'node:assert\/strict';\nimport { sign, verify } from '.\/hmac.js';\n\ntest('une signature valide est accept\u00e9e', () =&gt; {\n  const msg = 'donn\u00e9es importantes';\n  assert.equal(verify(msg, sign(msg)), true);\n});\n\ntest('un message modifi\u00e9 est rejet\u00e9', () =&gt; {\n  const sig = sign('original');\n  assert.equal(verify('falsifi\u00e9', sig), false);\n});\n\ntest('une signature tronqu\u00e9e est rejet\u00e9e sans erreur', () =&gt; {\n  assert.equal(verify('original', 'abcd'), false);\n});<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --env-file=.env --test\n\u2714 une signature valide est accept\u00e9e\n\u2714 un message modifi\u00e9 est rejet\u00e9\n\u2714 une signature tronqu\u00e9e est rejet\u00e9e sans erreur\n\u2139 tests 3\n\u2139 pass 3\n\u2139 fail 0<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-11-encoder-en-base64url-pour-des-en-tetes-compacts\">\u00c9tape 11 : encoder en base64url pour des en-t\u00eates compacts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;hexad\u00e9cimal occupe deux caract\u00e8res par octet. Pour des en-t\u00eates plus compacts, encodez en base64url, le format utilis\u00e9 par les JWT. Node le g\u00e8re nativement.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hmac.js (variante base64url)\nexport function signBase64url(message) {\n  return createHmac('sha256', HMAC_SECRET)\n    .update(message, 'utf8')\n    .digest('base64url');\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Une empreinte HMAC-SHA256 fait 64 caract\u00e8res en hexad\u00e9cimal, mais seulement 43 en base64url. Ce format \u00e9vite les caract\u00e8res <code>+<\/code>, <code>\/<\/code> et <code>=<\/code> qui posent probl\u00e8me dans les URL et les en-t\u00eates. Choisissez un format et restez coh\u00e9rent entre client et serveur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-12-faire-tourner-les-cles-sans-coupure\">\u00c9tape 12 : faire tourner les cl\u00e9s sans coupure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Une cl\u00e9 compromise doit pouvoir \u00eatre remplac\u00e9e sans interrompre le service. La technique standard consiste \u00e0 accepter plusieurs cl\u00e9s en v\u00e9rification tout en signant avec une seule.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ rotation.js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n\/\/ La premi\u00e8re cl\u00e9 sert \u00e0 signer ; toutes servent \u00e0 v\u00e9rifier\nconst KEYS = process.env.HMAC_KEYS.split(',');\n\nexport function signRotating(message) {\n  return createHmac('sha256', KEYS[0]).update(message).digest('hex');\n}\n\nexport function verifyRotating(message, received) {\n  const b = Buffer.from(received, 'hex');\n  return KEYS.some(key =&gt; {\n    const a = Buffer.from(\n      createHmac('sha256', key).update(message).digest('hex'), 'hex'\n    );\n    return a.length === b.length &amp;&amp; timingSafeEqual(a, b);\n  });\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour une rotation : ajoutez la nouvelle cl\u00e9 en t\u00eate de <code>HMAC_KEYS<\/code>, d\u00e9ployez, laissez les anciens clients migrer, puis retirez l&#8217;ancienne cl\u00e9 apr\u00e8s la p\u00e9riode de transition. L&#8217;ANSSI recommande une rotation p\u00e9riodique des cl\u00e9s sym\u00e9triques, et un remplacement imm\u00e9diat en cas de suspicion de compromission.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cas-dusage-concrets-dhmac-en-2026\">Cas d&#8217;usage concrets d&#8217;HMAC en 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Au-del\u00e0 de la th\u00e9orie, HMAC-SHA256 reste partout dans l&#8217;infrastructure web moderne. Le reconna\u00eetre vous \u00e9vite de r\u00e9inventer une roue d\u00e9j\u00e0 \u00e9prouv\u00e9e.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>V\u00e9rification de webhooks.<\/strong> Stripe, GitHub, Slack, Shopify et la plupart des plateformes signent leurs notifications sortantes avec HMAC pour prouver l&#8217;origine de chaque appel.<\/li>\n<li><strong>Signature de requ\u00eates d&#8217;API.<\/strong> Les API d&#8217;AWS (Signature Version 4) reposent sur des d\u00e9rivations HMAC en cha\u00eene pour authentifier chaque requ\u00eate sans transmettre la cl\u00e9 secr\u00e8te sur le r\u00e9seau.<\/li>\n<li><strong>Tokens de session JWT.<\/strong> L&#8217;algorithme HS256, le plus courant pour les JWT, n&#8217;est rien d&#8217;autre qu&#8217;HMAC-SHA256 appliqu\u00e9 \u00e0 l&#8217;en-t\u00eate et \u00e0 la charge utile du token.<\/li>\n<li><strong>Liens sign\u00e9s \u00e0 dur\u00e9e limit\u00e9e.<\/strong> Les URL de t\u00e9l\u00e9chargement temporaires (objets de stockage cloud, r\u00e9initialisation de mot de passe) int\u00e8grent souvent une signature HMAC avec date d&#8217;expiration.<\/li>\n<li><strong>Int\u00e9grit\u00e9 des cookies.<\/strong> Les cookies sign\u00e9s emp\u00eachent un client de modifier leur contenu sans invalider la signature, sans pour autant chiffrer la valeur.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Dans chacun de ces cas, le motif est identique \u00e0 celui construit dans ce tutoriel : une cl\u00e9 secr\u00e8te partag\u00e9e, une cha\u00eene sign\u00e9e bien d\u00e9finie, et une v\u00e9rification en temps constant. Ma\u00eetriser le m\u00e9canisme une fois vous ouvre l&#8217;ensemble de ces int\u00e9grations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-pieges-courants-a-eviter-avec-hmac\">5 pi\u00e8ges courants \u00e0 \u00e9viter avec HMAC<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ces erreurs reviennent constamment dans les revues de code. Chacune compromet la s\u00e9curit\u00e9 de l&#8217;authentification.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Comparer avec <code>===<\/code> au lieu de <code>timingSafeEqual<\/code>.<\/strong> La comparaison de cha\u00eenes fuit le temps et expose \u00e0 une attaque par chronom\u00e9trage. Comparez toujours des buffers en temps constant.<\/li>\n<li><strong>Signer le corps pars\u00e9 plut\u00f4t que le corps brut.<\/strong> Un <code>JSON.parse<\/code> suivi d&#8217;un <code>JSON.stringify<\/code> peut r\u00e9ordonner les cl\u00e9s ou modifier l&#8217;espacement. La signature ne correspond alors plus. Signez toujours les octets bruts re\u00e7us.<\/li>\n<li><strong>Stocker la cl\u00e9 dans le code source.<\/strong> Une cl\u00e9 committ\u00e9e dans Git reste expos\u00e9e \u00e0 vie via l&#8217;historique, m\u00eame apr\u00e8s suppression. Utilisez des variables d&#8217;environnement ou un gestionnaire de secrets.<\/li>\n<li><strong>Omettre l&#8217;horodatage.<\/strong> Sans fen\u00eatre temporelle, une requ\u00eate sign\u00e9e intercept\u00e9e peut \u00eatre rejou\u00e9e ind\u00e9finiment. Liez toujours un timestamp \u00e0 la signature.<\/li>\n<li><strong>R\u00e9utiliser la cl\u00e9 HMAC pour le chiffrement.<\/strong> Une cl\u00e9 d&#8217;authentification et une cl\u00e9 de chiffrement doivent rester distinctes. M\u00e9langer les usages affaiblit les deux.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"depannage-8-problemes-frequents-et-leurs-solutions\">D\u00e9pannage : 8 probl\u00e8mes fr\u00e9quents et leurs solutions<\/h2>\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>Signature invalide<\/code> alors que tout semble correct<\/td><td>Le serveur signe le corps pars\u00e9, pas le brut<\/td><td>Capturer <code>req.rawBody<\/code> dans <code>express.json({ verify })<\/code><\/td><\/tr><tr><td><code>Input buffers must have the same byte length<\/code><\/td><td><code>timingSafeEqual<\/code> re\u00e7oit des buffers de tailles diff\u00e9rentes<\/td><td>Comparer les longueurs avant l&#8217;appel et renvoyer <code>false<\/code><\/td><\/tr><tr><td>La signature change \u00e0 chaque ex\u00e9cution<\/td><td>Un horodatage ou un nonce est inclus dans le message<\/td><td>Comportement normal ; comparez via <code>verify<\/code>, pas par \u00e9galit\u00e9 brute<\/td><\/tr><tr><td><code>HMAC_SECRET manquant<\/code> au d\u00e9marrage<\/td><td>Oubli du drapeau <code>--env-file=.env<\/code><\/td><td>Lancer <code>node --env-file=.env server.js<\/code><\/td><\/tr><tr><td>Signatures diff\u00e9rentes entre client et serveur<\/td><td>Encodages distincts (hex contre base64url)<\/td><td>Aligner le format de <code>digest()<\/code> des deux c\u00f4t\u00e9s<\/td><\/tr><tr><td>Webhook Stripe toujours rejet\u00e9<\/td><td>Usage de la cl\u00e9 API au lieu du <em>endpoint secret<\/em><\/td><td>Utiliser le secret du webhook (pr\u00e9fixe <code>whsec_<\/code>)<\/td><\/tr><tr><td><code>401<\/code> sur des requ\u00eates anciennes<\/td><td>D\u00e9calage d&#8217;horloge sup\u00e9rieur \u00e0 la tol\u00e9rance<\/td><td>Synchroniser via NTP ou \u00e9largir la fen\u00eatre<\/td><\/tr><tr><td>Caract\u00e8res <code>+<\/code> ou <code>\/<\/code> cass\u00e9s dans l&#8217;URL<\/td><td>Empreinte en base64 standard plac\u00e9e dans une URL<\/td><td>Passer \u00e0 <code>digest('base64url')<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"deboguer-une-signature-qui-ne-correspond-pas\">D\u00e9boguer une signature qui ne correspond pas<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Quand une v\u00e9rification \u00e9choue, journalisez la cha\u00eene exacte sign\u00e9e des deux c\u00f4t\u00e9s, jamais la cl\u00e9. Neuf fois sur dix, le probl\u00e8me vient d&#8217;une diff\u00e9rence d&#8217;un seul octet : un espace en trop, un saut de ligne, ou un ordre de cl\u00e9s JSON diff\u00e9rent. Comparez les deux <code>signedPayload<\/code> caract\u00e8re par caract\u00e8re pour isoler la divergence.<\/p>\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\">Une fois les bases en place, ces optimisations renforcent la robustesse en production.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>D\u00e9river des cl\u00e9s par contexte avec HKDF.<\/strong> Plut\u00f4t qu&#8217;une cl\u00e9 unique partout, utilisez <code>crypto.hkdfSync<\/code> pour d\u00e9river des sous-cl\u00e9s distinctes par usage (webhooks, API interne, cookies) \u00e0 partir d&#8217;un secret ma\u00eetre. Une fuite reste ainsi cloisonn\u00e9e.<\/li>\n<li><strong>Limiter le d\u00e9bit sur les \u00e9checs de signature.<\/strong> Couplez la v\u00e9rification HMAC \u00e0 une limitation de d\u00e9bit par IP. Un pic d&#8217;\u00e9checs signale une tentative de for\u00e7age et m\u00e9rite une alerte.<\/li>\n<li><strong>Pr\u00e9f\u00e9rer SHA-256 \u00e0 SHA-512 sur le web.<\/strong> Pour les messages courts typiques d&#8217;une API, SHA-256 suffit largement et reste un peu plus rapide que SHA-512 sur la plupart des architectures 64 bits r\u00e9centes. SHA-3 demeure plus lent en logiciel pur.<\/li>\n<li><strong>Hacher les gros fichiers par flux.<\/strong> Pour signer un fichier volumineux, alimentez l&#8217;objet HMAC par morceaux avec un <code>stream<\/code> plut\u00f4t que de charger tout le contenu en m\u00e9moire.<\/li>\n<li><strong>Documenter le sch\u00e9ma de signature.<\/strong> Pr\u00e9cisez noir sur blanc quels champs sont sign\u00e9s, dans quel ordre, et avec quel s\u00e9parateur. Les divergences de format sont la premi\u00e8re cause de bugs d&#8217;int\u00e9gration.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hmac-et-lhorizon-post-quantique\">HMAC et l&#8217;horizon post-quantique<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bonne nouvelle pour la p\u00e9rennit\u00e9 : HMAC-SHA256 r\u00e9siste bien aux ordinateurs quantiques. L&#8217;algorithme de Grover ne r\u00e9duit la s\u00e9curit\u00e9 que de moiti\u00e9 en th\u00e9orie, laissant environ 128 bits de marge, ce qui reste hors de port\u00e9e. Contrairement \u00e0 RSA ou ECDSA, HMAC n&#8217;a pas besoin d&#8217;\u00eatre remplac\u00e9 dans la transition vers la cryptographie post-quantique. Vos signatures sym\u00e9triques restent valides \u00e0 long terme, comme votre <a href=\"\/fr\/certificat-ssl-certbot-nginx\/\">certificat SSL<\/a> prot\u00e8ge d\u00e9j\u00e0 le transport.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"comprendre-la-construction-interne-dhmac\">Comprendre la construction interne d&#8217;HMAC<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Comprendre ce qui se passe sous le capot aide \u00e0 \u00e9viter les mauvaises impl\u00e9mentations maison. HMAC ne se contente pas de hacher la concat\u00e9nation de la cl\u00e9 et du message. La RFC 2104 d\u00e9finit une construction \u00e0 deux passes qui neutralise les faiblesses des fonctions de hachage Merkle-Damg\u00e5rd.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le m\u00e9canisme d\u00e9rive deux blocs de remplissage \u00e0 partir de la cl\u00e9 : un remplissage interne <code>ipad<\/code> (l&#8217;octet 0x36 r\u00e9p\u00e9t\u00e9) et un remplissage externe <code>opad<\/code> (l&#8217;octet 0x5c r\u00e9p\u00e9t\u00e9). La cl\u00e9 est d&#8217;abord ajust\u00e9e \u00e0 la taille de bloc de la fonction de hachage (64 octets pour SHA-256), par hachage si elle est trop longue, par remplissage de z\u00e9ros si elle est trop courte. La formule compl\u00e8te s&#8217;\u00e9crit ainsi :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HMAC(K, m) = H( (K' \u2295 opad) || H( (K' \u2295 ipad) || m ) )\n\nK'   : cl\u00e9 ajust\u00e9e \u00e0 la taille de bloc\nH    : fonction de hachage (ici SHA-256)\n\u2295    : OU exclusif (XOR)\n||   : concat\u00e9nation<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le double hachage est ce qui rend HMAC immun aux attaques par extension de longueur. Comme la passe externe re-hache le r\u00e9sultat de la passe interne avec un remplissage diff\u00e9rent, un attaquant ne peut pas prolonger le message en s&#8217;appuyant sur l&#8217;\u00e9tat interne de la fonction. C&#8217;est pr\u00e9cis\u00e9ment la raison pour laquelle vous ne devez jamais r\u00e9impl\u00e9menter HMAC \u00e0 la main : la moindre erreur sur le remplissage ou l&#8217;ajustement de cl\u00e9 casse cette garantie. Le module <code>node:crypto<\/code>, lui, s&#8217;appuie sur OpenSSL, dont l&#8217;impl\u00e9mentation est audit\u00e9e et \u00e9prouv\u00e9e depuis des ann\u00e9es.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cette construction explique aussi pourquoi la longueur de cl\u00e9 recommand\u00e9e correspond \u00e0 la taille de sortie du hachage. Une cl\u00e9 plus longue que la taille de bloc est d&#8217;abord hach\u00e9e, ce qui la ram\u00e8ne \u00e0 la taille de sortie sans gain de s\u00e9curit\u00e9. Une cl\u00e9 de 32 octets pour SHA-256 atteint donc le maximum d&#8217;entropie utile sans gaspillage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"mesurer-les-performances-dhmac-en-node-js\">Mesurer les performances d&#8217;HMAC en Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC-SHA256 est rapide, mais il vaut mieux mesurer que supposer. Voici un micro-benchmark qui compte le nombre de signatures par seconde sur votre machine. Mesurez toujours sur le mat\u00e9riel de production r\u00e9el, car les chiffres varient fortement selon le processeur.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ bench.js\nimport { sign } from '.\/hmac.js';\n\nconst message = 'commande:1042;montant:49.90';\nconst iterations = 1_000_000;\n\nconst start = process.hrtime.bigint();\nfor (let i = 0; i &lt; iterations; i++) sign(message + i);\nconst end = process.hrtime.bigint();\n\nconst ms = Number(end - start) \/ 1e6;\nconsole.log(`${iterations} signatures en ${ms.toFixed(0)} ms`);\nconsole.log(`${Math.round(iterations \/ (ms \/ 1000))} signatures\/s`);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sur une machine de d\u00e9veloppement r\u00e9cente, ce benchmark traite couramment plusieurs centaines de milliers de signatures par seconde. La latence par op\u00e9ration se compte en microsecondes, n\u00e9gligeable face au temps d&#8217;un aller-retour r\u00e9seau. Le tableau ci-dessous compare l&#8217;ordre de grandeur des principaux m\u00e9canismes pour signer un message court.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>M\u00e9canisme<\/th><th>Co\u00fbt relatif par op\u00e9ration<\/th><th>Usage typique<\/th><th>Bon choix pour une API ?<\/th><\/tr><\/thead><tbody><tr><td>HMAC-SHA256<\/td><td>Tr\u00e8s faible<\/td><td>Signature de requ\u00eates, webhooks<\/td><td>Oui<\/td><\/tr><tr><td>HMAC-SHA512<\/td><td>Faible<\/td><td>Messages tr\u00e8s longs<\/td><td>Oui, marginalement plus lent<\/td><\/tr><tr><td>RSA-2048 (signature)<\/td><td>\u00c9lev\u00e9<\/td><td>Signature v\u00e9rifiable publiquement<\/td><td>Seulement si l&#8217;asym\u00e9trie est requise<\/td><\/tr><tr><td>ECDSA P-256<\/td><td>Moyen<\/td><td>Signature asym\u00e9trique compacte<\/td><td>Seulement si l&#8217;asym\u00e9trie est requise<\/td><\/tr><tr><td>bcrypt (co\u00fbt 12)<\/td><td>Tr\u00e8s \u00e9lev\u00e9 (volontaire)<\/td><td>Stockage de mots de passe<\/td><td>Non, jamais pour signer des requ\u00eates<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Pour la grande majorit\u00e9 des API, la signature HMAC n&#8217;est jamais le goulot d&#8217;\u00e9tranglement : la s\u00e9rialisation JSON, l&#8217;acc\u00e8s \u00e0 la base de donn\u00e9es et la latence r\u00e9seau dominent largement. N&#8217;optimisez donc pas pr\u00e9matur\u00e9ment le choix de l&#8217;algorithme de hachage. SHA-256 offre le meilleur compromis entre s\u00e9curit\u00e9, compatibilit\u00e9 universelle et vitesse, ce qui en fait le d\u00e9faut raisonnable en 2026. R\u00e9servez SHA-512 aux cas o\u00f9 vous signez r\u00e9guli\u00e8rement des charges utiles de plusieurs m\u00e9gaoctets, o\u00f9 son d\u00e9bit par octet peut devenir un l\u00e9ger avantage sur les processeurs 64 bits.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"le-projet-complet-structure-des-fichiers\">Le projet complet : structure des fichiers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Voici l&#8217;arborescence finale du projet fonctionnel construit au fil des \u00e9tapes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>hmac-api\/\n\u251c\u2500\u2500 .env              # cl\u00e9 secr\u00e8te (jamais committ\u00e9e)\n\u251c\u2500\u2500 .gitignore        # contient .env et node_modules\n\u251c\u2500\u2500 package.json      # \"type\": \"module\"\n\u251c\u2500\u2500 config.js         # chargement et validation de la cl\u00e9\n\u251c\u2500\u2500 keygen.js         # g\u00e9n\u00e9ration de cl\u00e9\n\u251c\u2500\u2500 hmac.js           # sign, verify, timestamp\n\u251c\u2500\u2500 webhook.js        # v\u00e9rification type Stripe\n\u251c\u2500\u2500 server.js         # API Express prot\u00e9g\u00e9e par HMAC\n\u251c\u2500\u2500 client.js         # client qui signe ses requ\u00eates\n\u251c\u2500\u2500 rotation.js       # rotation multi-cl\u00e9s\n\u2514\u2500\u2500 hmac.test.js      # tests unitaires natifs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Chaque fichier reste sous 30 lignes. Toute la logique cryptographique tient dans <code>hmac.js<\/code>, sans aucune d\u00e9pendance externe au-del\u00e0 d&#8217;Express pour la d\u00e9monstration. C&#8217;est la force de <code>node:crypto<\/code> : un code minimal, auditable et performant.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"foire-aux-questions-sur-hmac-en-node-js\">Foire aux questions sur HMAC en Node.js<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quelle-est-la-difference-entre-hmac-et-un-hachage-sha-256-simple\">Quelle est la diff\u00e9rence entre HMAC et un hachage SHA-256 simple ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SHA-256 seul ne prend aucune cl\u00e9 : n&#8217;importe qui peut recalculer l&#8217;empreinte, donc il n&#8217;authentifie rien. HMAC combine une cl\u00e9 secr\u00e8te au message, ce qui garantit que seul le d\u00e9tenteur de la cl\u00e9 peut produire une signature valide. HMAC bloque aussi les attaques par extension de longueur, contre lesquelles le SHA-256 brut concat\u00e9n\u00e9 \u00e0 une cl\u00e9 reste vuln\u00e9rable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"faut-il-une-bibliotheque-externe-pour-hmac-en-node-js\">Faut-il une biblioth\u00e8que externe pour HMAC en Node.js ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non. Le module natif <code>node:crypto<\/code> fournit <code>createHmac<\/code> et <code>timingSafeEqual<\/code>, tout ce qui est n\u00e9cessaire. \u00c9vitez les paquets comme <code>crypto-js<\/code> c\u00f4t\u00e9 serveur : ils sont plus lents et ajoutent une surface d&#8217;attaque inutile alors que la fonctionnalit\u00e9 existe nativement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quelle-longueur-de-cle-hmac-choisir\">Quelle longueur de cl\u00e9 HMAC choisir ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Au minimum 128 bits selon l&#8217;ANSSI, et 256 bits (32 octets) comme choix prudent standard. G\u00e9n\u00e9rez la cl\u00e9 avec <code>crypto.randomBytes(32)<\/code> et jamais \u00e0 la main. Une cl\u00e9 id\u00e9ale a une longueur \u00e9gale \u00e0 celle de la sortie de la fonction de hachage, soit 32 octets pour SHA-256.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hmac-peut-il-servir-pour-les-mots-de-passe\">HMAC peut-il servir pour les mots de passe ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non, ce n&#8217;est pas son r\u00f4le. HMAC est rapide, ce qui devient un d\u00e9faut pour le stockage de mots de passe, o\u00f9 la lenteur prot\u00e8ge contre le for\u00e7age. Utilisez <a href=\"\/fr\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt<\/a> ou Argon2, con\u00e7us sp\u00e9cifiquement pour ralentir les attaques par dictionnaire.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pourquoi-mes-signatures-ne-correspondent-elles-pas-entre-client-et-serveur\">Pourquoi mes signatures ne correspondent-elles pas entre client et serveur ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La cause la plus fr\u00e9quente est une diff\u00e9rence dans la cha\u00eene sign\u00e9e : le serveur signe souvent le corps repars\u00e9 alors que le client signe la cha\u00eene d&#8217;origine. Signez toujours le corps brut identique des deux c\u00f4t\u00e9s, et v\u00e9rifiez que l&#8217;encodage de sortie (<code>hex<\/code> ou <code>base64url<\/code>) est le m\u00eame.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"hmac-est-il-vulnerable-aux-ordinateurs-quantiques\">HMAC est-il vuln\u00e9rable aux ordinateurs quantiques ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pas en pratique. L&#8217;algorithme de Grover divise par deux la s\u00e9curit\u00e9 th\u00e9orique, laissant environ 128 bits effectifs pour HMAC-SHA256, ce qui reste hors d&#8217;atteinte. Aucune migration n&#8217;est n\u00e9cessaire, contrairement aux signatures asym\u00e9triques RSA et ECDSA menac\u00e9es par l&#8217;algorithme de Shor.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"comment-verifier-un-webhook-github-ou-stripe\">Comment v\u00e9rifier un webhook GitHub ou Stripe ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">R\u00e9cup\u00e9rez le corps brut de la requ\u00eate, calculez <code>HMAC-SHA256<\/code> avec le secret du webhook (pas la cl\u00e9 API), et comparez avec la signature de l&#8217;en-t\u00eate via <code>timingSafeEqual<\/code>. Stripe signe <code>timestamp.payload<\/code>, GitHub signe directement le corps avec l&#8217;en-t\u00eate <code>X-Hub-Signature-256<\/code>. Le principe reste identique.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"faut-il-chiffrer-le-message-en-plus-de-le-signer\">Faut-il chiffrer le message en plus de le signer ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC garantit l&#8217;int\u00e9grit\u00e9 et l&#8217;authenticit\u00e9, mais pas la confidentialit\u00e9 : le message reste lisible. Si le contenu est sensible, combinez signature et chiffrement, ou appuyez-vous sur TLS pour le transport et un chiffrement authentifi\u00e9 comme AES-GCM pour les donn\u00e9es au repos.<\/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\">\n<li><a href=\"\/fr\/authentification-jwt-nodejs\/\">Authentification JWT en Node.js : 12 \u00e9tapes<\/a><\/li>\n<li><a href=\"\/fr\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt Node.js : hacher un mot de passe en 12 \u00e9tapes<\/a><\/li>\n<li><a href=\"\/fr\/oauth2-openid-connect-nodejs\/\">OAuth2 en Node.js : flux s\u00e9curis\u00e9 en 12 \u00e9tapes<\/a><\/li>\n<li><a href=\"\/fr\/certificat-ssl-certbot-nginx\/\">Certificat SSL gratuit avec Certbot : 11 \u00e9tapes<\/a><\/li>\n<li><a href=\"\/fr\/chiffrer-fichier-gpg-tutoriel\/\">GPG : chiffrer fichiers et emails en 12 \u00e9tapes<\/a><\/li>\n<li><a href=\"\/fr\/securite-des-mots-de-passe\/\">S\u00e9curit\u00e9 des mots de passe : longueur, hachage et gestionnaires<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">HMAC-SHA256 reste l&#8217;un des outils les plus simples et les plus robustes de la bo\u00eete \u00e0 outils cryptographique. Avec une bonne cl\u00e9, une comparaison en temps constant et un horodatage anti-rejeu, vous disposez d&#8217;une authentification de message solide, performante et durable, le tout sans une seule d\u00e9pendance externe.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>La signature de messages avec HMAC-SHA256 prot\u00e8ge vos API, vos webhooks et vos tokens contre la falsification. Contrairement \u00e0 un simple hachage SHA-256, HMAC combine une cl\u00e9 secr\u00e8te au message,\u2026<\/p>\n","protected":false},"author":6,"featured_media":154,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,2],"tags":[],"class_list":["post-153","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-10","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/153","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\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/comments?post=153"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/153\/revisions"}],"predecessor-version":[{"id":155,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/153\/revisions\/155"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media\/154"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media?parent=153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/categories?post=153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/tags?post=153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}