Une API mal signée, c’est une porte ouverte. En juillet 2026, plusieurs incidents rapportés sur des places de marché crypto et des plateformes SaaS européennes ont encore montré ce qui arrive quand des webhooks ou des requêtes API circulent sans vérification d’intégrité : un attaquant intercepte, rejoue ou falsifie une requête, et le serveur l’accepte sans broncher. HMAC (Hash-based Message Authentication Code) règle ce problème avec un algorithme vieux de près de trente ans, toujours recommandé par le NIST et largement utilisé par AWS, Stripe, GitHub ou Shopify pour signer leurs requêtes et leurs webhooks. Ce tutoriel vous montre, étape par étape, comment implémenter HMAC-SHA256 pour sécuriser une API REST : génération de clé, signature côté client, vérification côté serveur, protection anti-rejeu, rotation des secrets et déploiement en production. À la fin, vous aurez un projet fonctionnel complet, testé, et prêt à être adapté à votre stack.

Qu’est-ce que HMAC-SHA256 et pourquoi l’utiliser pour sécuriser une API

HMAC signifie Hash-based Message Authentication Code. Concrètement, c’est une fonction qui combine un message (votre requête HTTP, un payload JSON, un fichier) avec une clé secrète partagée, puis passe le tout dans une fonction de hachage cryptographique, le plus souvent SHA-256. Le résultat est une empreinte unique de 256 bits (32 octets, généralement affichée en hexadécimal sur 64 caractères) qui prouve deux choses à la fois : que le message n’a pas été modifié en transit, et qu’il a bien été généré par quelqu’un qui connaît la clé secrète.

L’algorithme est décrit dans la RFC 2104, publiée en février 1997, et standardisé officiellement par le NIST dans le document FIPS 198-1. Trente ans plus tard, HMAC reste un pilier de l’authentification d’API : AWS l’utilise dans Signature Version 4 pour signer chaque appel à ses services, Stripe et GitHub l’utilisent pour authentifier leurs webhooks entrants, et de nombreuses API de plateformes d’échange crypto (comme celles évoquées dans notre comparatif HMAC vs signature numérique) s’appuient dessus pour signer les ordres de trading.

La force de HMAC tient à sa simplicité. Contrairement à une signature numérique RSA ou ECDSA, il n’y a pas de paire clé publique/clé privée, pas de certificat, pas d’infrastructure PKI à gérer. Client et serveur partagent la même clé secrète, chacun calcule le HMAC de son côté et compare le résultat. C’est rapide, léger en CPU, et suffisant pour l’immense majorité des cas d’usage API-à-API où les deux parties peuvent se faire confiance sur la distribution initiale de la clé.

Prérequis : outils, langages et versions nécessaires

Avant de commencer, assurez-vous d’avoir l’environnement suivant. Ce tutoriel utilise Node.js pour le serveur et Python pour le client, mais les principes s’appliquent à n’importe quel langage disposant d’une bibliothèque cryptographique standard.

  • Node.js 22 LTS ou plus récent (le module crypto est inclus nativement, aucune dépendance externe requise)
  • Python 3.12 ou plus récent (les modules hmac et hashlib font partie de la bibliothèque standard)
  • OpenSSL 3.x pour générer des clés secrètes aléatoires en ligne de commande
  • curl ou Postman pour tester les requêtes signées
  • Un éditeur de code (VS Code, JetBrains, ou équivalent)
  • Une base de connaissances sur les requêtes HTTP et les en-têtes personnalisés

Aucune de ces briques ne nécessite de package tiers pour la partie cryptographique : c’est un des atouts de HMAC, il est disponible nativement dans quasiment tous les langages modernes sans ajouter de surface d’attaque via une dépendance externe.

Comment fonctionne HMAC : la théorie en bref

Techniquement, HMAC applique la fonction de hachage deux fois, avec deux versions dérivées de la clé (un padding interne et un padding externe), ce qui la protège contre les attaques par extension de longueur qui touchent un simple hachage SHA-256(clé + message). C’est une différence essentielle : concaténer naïvement une clé secrète et un message avant de les hacher n’offre pas les mêmes garanties de sécurité que HMAC, même si le résultat ressemble à la même chose en apparence.

HMAC n’est pas un simple hachage

Un hachage seul (SHA-256 d’un mot de passe, par exemple) ne prouve rien sur l’auteur du message : n’importe qui peut recalculer un SHA-256. HMAC ajoute la dimension d’authentification parce que le calcul est impossible à reproduire sans connaître la clé secrète. C’est ce qui en fait un mécanisme d’authentification de requêtes, et pas seulement un contrôle d’intégrité.

Pourquoi SHA-256 plutôt que SHA-1 ou MD5

HMAC peut techniquement s’appuyer sur n’importe quelle fonction de hachage (HMAC-MD5, HMAC-SHA1 existent toujours dans les bibliothèques par compatibilité), mais SHA-1 et MD5 sont aujourd’hui considérés obsolètes pour tout nouveau développement en raison de faiblesses connues sur la fonction de hachage elle-même. HMAC-SHA256 est le standard de facto recommandé pour toute nouvelle implémentation, avec HMAC-SHA3-256 comme alternative pour les environnements qui veulent diversifier leur famille d’algorithmes (voir notre comparatif SHA-256 vs SHA-3 pour les différences de performance entre les deux).

HMAC face à JWT, OAuth 2.0 et la signature numérique

Avant de coder, il faut savoir quand HMAC est le bon choix. Ce n’est pas un concurrent direct d’OAuth 2.0 (qui gère la délégation d’autorisation) mais plutôt une brique d’authentification et d’intégrité que l’on peut combiner avec d’autres mécanismes. Voici comment il se positionne face aux alternatives les plus courantes.

MécanismeType de cléCas d’usage typiqueComplexité de mise en œuvre
HMAC-SHA256Clé symétrique partagéeSignature de requêtes API, webhooks (Stripe, GitHub, AWS SigV4)Faible
JWT (HS256)Clé symétrique partagéeJetons de session, autorisation statelessFaible à moyenne
JWT (RS256/ES256)Paire clé publique/privéeJetons vérifiables par des tiers sans partager de secretMoyenne
OAuth 2.0Jetons d’accès + refreshDélégation d’autorisation utilisateur (login tiers)Élevée
Signature numérique (ECDSA, Ed25519)Paire clé publique/privéeNon-répudiation, signature de documents ou de transactionsMoyenne à élevée

Le point clé : HMAC suppose que les deux parties partagent déjà un secret de façon sécurisée (au moment de la création du compte API, par exemple). Si vous devez vérifier une signature sans jamais partager de secret avec l’émetteur (cas d’un tiers qui vérifie une transaction blockchain, par exemple), une signature numérique à clé publique s’impose. Pour un comparatif chiffré des performances entre les deux approches, notre article HMAC vs signature numérique détaille les écarts de vitesse mesurés entre HMAC et ECDSA.

Étape 1 : Installer l’environnement de développement

Créez un dossier de projet avec deux sous-dossiers : server/ pour l’API Node.js et client/ pour le script Python qui signera les requêtes.

mkdir hmac-api-demo && cd hmac-api-demo
mkdir server client
cd server && npm init -y && npm install express
cd ../client && python3 -m venv venv && source venv/bin/activate
pip install requests

Notez que express et requests sont uniquement là pour simplifier la démo HTTP. Les modules crypto (Node.js) et hmac/hashlib (Python) qui font le travail cryptographique sont natifs, vous n’installez aucune bibliothèque de sécurité tierce.

Étape 2 : Générer et stocker une clé secrète HMAC robuste

La sécurité de tout le système repose sur l’entropie de la clé secrète. Une clé faible (un mot de passe, une chaîne courte) rend l’implémentation vulnérable à une attaque par force brute hors ligne, même si l’algorithme HMAC lui-même est solide. Générez une clé aléatoire d’au moins 32 octets avec OpenSSL.

openssl rand -hex 32
# Exemple de sortie (à ne jamais réutiliser telle quelle) :
# 9f2c1e8a7b4d6f0a3c5e8b1d4f7a0c3e6b9d2f5a8c1e4b7d0a3f6c9e2b5d8a1f

Ne stockez jamais cette clé dans votre code source ni dans un fichier versionné dans Git. Placez-la dans une variable d’environnement, un gestionnaire de secrets (AWS Secrets Manager, HashiCorp Vault, ou l’équivalent de votre fournisseur cloud), ou un fichier .env explicitement ajouté à votre .gitignore.

# .env (server/)
HMAC_SECRET_KEY=9f2c1e8a7b4d6f0a3c5e8b1d4f7a0c3e6b9d2f5a8c1e4b7d0a3f6c9e2b5d8a1f
HMAC_HEADER_NAME=X-Signature-256
HMAC_TIMESTAMP_TOLERANCE=300

Étape 3 : Implémenter la signature HMAC côté serveur (Node.js)

Créez un fichier server/index.js. Le serveur reçoit la requête, recalcule la signature attendue à partir du corps de la requête et de la clé secrète, puis la compare à la signature envoyée par le client dans l’en-tête HTTP.

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json({
  verify: (req, res, buf) => { req.rawBody = buf; }
}));

const SECRET_KEY = process.env.HMAC_SECRET_KEY;

function computeSignature(rawBody, secret) {
  return crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
}

app.post('/api/orders', (req, res) => {
  const receivedSignature = req.get('X-Signature-256');
  if (!receivedSignature) {
    return res.status(401).json({ error: 'Signature manquante' });
  }

  const expectedSignature = computeSignature(req.rawBody, SECRET_KEY);

  const receivedBuffer = Buffer.from(receivedSignature, 'hex');
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');

  if (
    receivedBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(receivedBuffer, expectedBuffer)
  ) {
    return res.status(401).json({ error: 'Signature invalide' });
  }

  res.json({ status: 'ok', order: req.body });
});

app.listen(3000, () => console.log('Serveur HMAC sur le port 3000'));

Deux détails critiques dans ce code : on signe le rawBody brut (le buffer exact reçu sur le réseau), pas l’objet JSON reparsé par Express, et on utilise crypto.timingSafeEqual() plutôt qu’une comparaison de chaînes classique. On y revient en détail à l’étape suivante.

Étape 4 : Signer une requête côté client (Python)

Côté client, on construit le corps de la requête, on le signe avec la même clé secrète, puis on envoie la signature dans l’en-tête HTTP. Créez client/send_order.py.

import hmac
import hashlib
import json
import requests

SECRET_KEY = "9f2c1e8a7b4d6f0a3c5e8b1d4f7a0c3e6b9d2f5a8c1e4b7d0a3f6c9e2b5d8a1f"
API_URL = "http://localhost:3000/api/orders"

def sign_payload(payload_bytes: bytes, secret: str) -> str:
    return hmac.new(
        secret.encode("utf-8"),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()

payload = {"symbol": "BTC-EUR", "side": "buy", "quantity": 0.05}
body_bytes = json.dumps(payload, separators=(",", ":")).encode("utf-8")

signature = sign_payload(body_bytes, SECRET_KEY)

response = requests.post(
    API_URL,
    data=body_bytes,
    headers={
        "Content-Type": "application/json",
        "X-Signature-256": signature
    }
)

print("Statut :", response.status_code)
print("Réponse :", response.json())

Le point le plus fréquent de bug ici : le client doit sérialiser le JSON exactement de la même manière que ce que le serveur reçoit en rawBody, séparateurs compris. Un espace en trop entre les clés JSON, et les deux signatures ne correspondront jamais alors même que le contenu logique est identique.

Étape 5 : Vérifier la signature avec une comparaison à temps constant

Comparer deux chaînes avec l’opérateur === ou == classique s’arrête au premier caractère différent. Un attaquant qui mesure le temps de réponse du serveur sur des milliers de tentatives peut, en théorie, déduire la signature attendue caractère par caractère : c’est une attaque par canal auxiliaire (timing attack). C’est pour cette raison que crypto.timingSafeEqual() en Node.js et hmac.compare_digest() en Python existent : ils comparent toujours l’intégralité des deux buffers, quel que soit l’endroit où la différence apparaît.

# Équivalent Python pour un service qui vérifie côté serveur
import hmac

def verify_signature(raw_body: bytes, received_signature: str, secret: str) -> bool:
    expected_signature = hmac.new(
        secret.encode("utf-8"), raw_body, "sha256"
    ).hexdigest()
    return hmac.compare_digest(expected_signature, received_signature)

Ne réécrivez jamais votre propre fonction de comparaison “sécurisée” : utilisez systématiquement la fonction fournie par la bibliothèque standard de votre langage, elle a été auditée pour ce cas précis.

Étape 6 : Ajouter un timestamp et un nonce contre les attaques par rejeu

HMAC seul garantit l’intégrité et l’authenticité, mais pas la fraîcheur. Un attaquant qui capture une requête signée valide peut la rejouer indéfiniment (une attaque par rejeu), tant que la signature reste valide. La parade consiste à inclure un timestamp dans le message signé et à rejeter toute requête trop ancienne.

// server/index.js — ajout du contrôle de fraîcheur
const TOLERANCE_SECONDS = parseInt(process.env.HMAC_TIMESTAMP_TOLERANCE || '300', 10);

app.post('/api/orders', (req, res) => {
  const receivedSignature = req.get('X-Signature-256');
  const timestamp = req.get('X-Timestamp');

  if (!receivedSignature || !timestamp) {
    return res.status(401).json({ error: 'En-têtes manquants' });
  }

  const now = Math.floor(Date.now() / 1000);
  const requestTime = parseInt(timestamp, 10);

  if (Math.abs(now - requestTime) > TOLERANCE_SECONDS) {
    return res.status(401).json({ error: 'Requête expirée ou horloge désynchronisée' });
  }

  const signedPayload = Buffer.concat([
    Buffer.from(timestamp + '.'),
    req.rawBody
  ]);
  const expectedSignature = computeSignature(signedPayload, SECRET_KEY);

  // ... comparaison à temps constant comme à l'étape 5
});

En signant timestamp + '.' + rawBody plutôt que le seul corps, on lie la signature au moment précis de l’envoi : une requête capturée et rejouée plus tard échouera automatiquement, même si la signature elle-même était mathématiquement correcte au moment de l’émission. Une tolérance de 300 secondes (5 minutes) est une valeur de départ raisonnable qui absorbe les décalages d’horloge normaux entre client et serveur, à ajuster selon la sensibilité de votre API.

Étape 7 : Sécuriser les webhooks entrants avec HMAC

Le cas d’usage le plus courant de HMAC aujourd’hui n’est pas votre propre API sortante, mais la vérification des webhooks entrants d’un fournisseur tiers. Stripe signe chaque webhook avec HMAC-SHA256 dans l’en-tête Stripe-Signature, GitHub fait la même chose avec X-Hub-Signature-256, et la plupart des plateformes de paiement ou d’échange crypto suivent ce même schéma. Le principe de vérification est identique à celui du serveur que vous venez de construire : le fournisseur du webhook vous communique un secret unique lors de la configuration, et chaque requête entrante doit être vérifiée avant tout traitement.

La règle d’or : ne traitez jamais la charge utile d’un webhook (mise à jour de commande, confirmation de paiement, changement de statut) avant d’avoir validé sa signature. Placez la vérification HMAC en tout premier middleware de votre route webhook, avant même le parsing JSON métier, exactement comme illustré à l’étape 3.

Le site de référence webhooks.fyi recense les conventions de signature utilisées par les principaux fournisseurs SaaS : la plupart placent la signature dans un en-tête dédié (X-Hub-Signature-256, Stripe-Signature, X-Signature) plutôt que dans le corps de la requête, exactement le schéma que vous venez d’implémenter. Si vous exposez vos propres webhooks vers des partenaires, documentez clairement le nom de l’en-tête, l’algorithme utilisé et l’encodage attendu (hex ou base64) : c’est l’erreur d’intégration la plus fréquente côté consommateur du webhook.

Étape 8 : Faire tourner les clés secrètes sans interruption de service

Une clé HMAC ne doit pas rester statique indéfiniment : une rotation régulière (tous les 90 à 180 jours selon la criticité de l’API) limite l’impact d’une fuite éventuelle. Le défi technique, c’est que changer la clé d’un coup casse toutes les requêtes en vol signées avec l’ancienne clé. La solution standard consiste à accepter temporairement deux clés en parallèle.

function verifyWithKeyRotation(rawBody, receivedSignature, currentKey, previousKey) {
  const currentSig = computeSignature(rawBody, currentKey);
  if (safeCompare(currentSig, receivedSignature)) return true;

  if (previousKey) {
    const previousSig = computeSignature(rawBody, previousKey);
    if (safeCompare(previousSig, receivedSignature)) return true;
  }

  return false;
}

Concrètement : générez la nouvelle clé, déployez-la comme “clé courante” tout en gardant l’ancienne comme “clé précédente” pendant une fenêtre de transition (24 à 72 heures suffisent généralement pour qu’aucun client n’utilise plus l’ancien secret), communiquez le changement à vos intégrateurs, puis retirez complètement l’ancienne clé. Documentez ce processus : une rotation de clé mal préparée casse la production pour tous vos clients API en même temps.

Étape 9 : Journaliser, monitorer et alerter sur les échecs

Un pic soudain de signatures invalides est souvent le premier signal d’une attaque par force brute, d’un bug côté client, ou d’une clé compromise en cours d’exploitation. Journalisez systématiquement chaque échec de vérification HMAC avec l’IP source, l’horodatage et l’endpoint ciblé, sans jamais logger la clé secrète ni la signature complète en clair. Mettez en place une alerte si le taux d’échec dépasse un seuil anormal (par exemple plus de 20 échecs par minute depuis une même IP), et envisagez un rate-limiting agressif sur les routes protégées par HMAC pour ralentir toute tentative automatisée.

Le REST Security Cheat Sheet de l’OWASP recommande explicitement de combiner ce type de journalisation avec une limitation de débit par clé API et par adresse IP, plutôt qu’une limite globale sur l’ensemble du service : cela empêche un client compromis d’affecter la disponibilité des autres intégrateurs qui utilisent la même API. Pensez également à exposer un dashboard interne (Grafana, Datadog, ou équivalent) qui trace le taux d’échec de signature dans le temps : une dérive lente et continue peut trahir une clé progressivement devinée ou un client mal configuré qui n’a pas été mis à jour après une rotation.

Étape 10 : Écrire des tests unitaires et valider l’implémentation

Avant de déployer, testez systématiquement les cas limites : signature valide, signature invalide, en-tête manquant, timestamp expiré, corps de requête modifié après signature. Voici un exemple minimal avec le framework de test intégré de Node.js.

const test = require('node:test');
const assert = require('node:assert');
const crypto = require('crypto');

function computeSignature(body, secret) {
  return crypto.createHmac('sha256', secret).update(body).digest('hex');
}

test('la signature change si le corps change', () => {
  const secret = 'test-secret';
  const sig1 = computeSignature(Buffer.from('{"a":1}'), secret);
  const sig2 = computeSignature(Buffer.from('{"a":2}'), secret);
  assert.notStrictEqual(sig1, sig2);
});

test('la signature est identique pour un même corps et une même clé', () => {
  const secret = 'test-secret';
  const body = Buffer.from('{"a":1}');
  assert.strictEqual(computeSignature(body, secret), computeSignature(body, secret));
});

test('une signature générée avec une mauvaise clé est rejetée', () => {
  const body = Buffer.from('{"a":1}');
  const sigGood = computeSignature(body, 'bonne-cle');
  const sigBad = computeSignature(body, 'mauvaise-cle');
  assert.notStrictEqual(sigGood, sigBad);
});

Lancez ces tests avec node --test. Ajoutez ensuite des tests d’intégration qui envoient de vraies requêtes HTTP à votre serveur local, en couvrant explicitement le scénario “timestamp expiré” et “en-tête de signature absent”, deux cas que les développeurs oublient le plus souvent de tester.

Étape 11 : Déployer en production et gérer les secrets

En production, la clé HMAC ne doit jamais transiter par un fichier .env commité, une variable en dur dans un Dockerfile, ou un canal de communication non chiffré (email, Slack en clair). Utilisez un gestionnaire de secrets managé : AWS Secrets Manager ou Parameter Store, Google Secret Manager, Azure Key Vault, ou HashiCorp Vault en auto-hébergé. Injectez la clé au runtime via une variable d’environnement récupérée depuis ce gestionnaire, jamais via un fichier de configuration versionné.

Activez également HTTPS/TLS 1.3 en bout en bout : HMAC protège l’intégrité et l’authenticité du message, mais pas sa confidentialité. Sans TLS, un attaquant en position de man-in-the-middle peut toujours lire le contenu de la requête, même s’il ne peut pas la falsifier sans connaître la clé secrète.

Pièges courants à éviter

  • Signer le JSON reparsé plutôt que le corps brut. Si le serveur reparse puis re-sérialise le JSON avant de vérifier la signature, l’ordre des clés ou l’espacement peut changer, et la vérification échoue systématiquement même avec une signature légitime.
  • Comparer les signatures avec un opérateur d’égalité classique. Utilisez toujours une fonction de comparaison à temps constant (timingSafeEqual, compare_digest), jamais ===, == ou .equals() standard.
  • Réutiliser la même clé secrète pour plusieurs environnements. Une clé de développement, de staging et de production doivent être trois secrets distincts. Une fuite en dev ne doit jamais compromettre la prod.
  • Oublier le contrôle de fraîcheur (timestamp). Sans timestamp ni nonce, une requête capturée reste valide indéfiniment et peut être rejouée à volonté.
  • Générer une clé secrète trop courte ou prévisible. Un mot de passe humain, même complexe, n’a pas l’entropie d’une clé générée par un CSPRNG (générateur pseudo-aléatoire cryptographiquement sûr) comme openssl rand.
  • Logger la clé secrète ou la signature complète en clair. En cas de fuite de logs, cela équivaut à publier votre mot de passe API.

Dépannage : problèmes fréquents et leurs solutions

SymptômeCause probableSolution
La signature ne correspond jamais, même avec la bonne cléLe corps est reparsé/re-sérialisé avant vérification côté serveurSigner et vérifier le buffer brut (rawBody), pas l’objet JSON reconstruit
Format de sortie incohérent entre client et serveurEncodage différent (hex vs base64) des deux côtésStandardiser l’encodage de la signature (hex de préférence) des deux côtés
Les requêtes échouent uniquement en productionDécalage d’horloge (clock skew) entre serveurs si vous utilisez un timestampSynchroniser les horloges via NTP et augmenter la tolérance temporairement pour diagnostiquer
Signature valide en local mais rejetée derrière un proxy/CDNLe proxy modifie le corps de la requête (compression, réencodage)Désactiver la transformation du corps pour les routes signées ou signer avant le proxy
Taux anormal de signatures invalides en productionTentative de force brute ou clé compromiseActiver le rate-limiting, faire tourner la clé immédiatement, auditer les logs d’accès
La vérification échoue seulement pour certains caractères UnicodeEncodage de chaîne incohérent (UTF-8 vs Latin-1) avant hachageForcer l’encodage UTF-8 explicitement des deux côtés avant la conversion en bytes
Webhook du fournisseur tiers toujours rejetéMauvais secret utilisé (secret de test vs secret de production)Vérifier dans le dashboard du fournisseur quel environnement génère quel secret
Les tests unitaires passent mais la prod échoueLe corps réel contient des caractères que les tests ne couvrent pas (emoji, retours ligne)Ajouter des cas de test avec des payloads réels capturés en production (anonymisés)

Astuces avancées pour aller plus loin

Une fois l’implémentation de base en place, plusieurs raffinements renforcent la robustesse du système. Premièrement, envisagez d’inclure un identifiant de clé (kid) dans l’en-tête de signature plutôt que de deviner quelle clé a servi à signer : cela simplifie énormément la gestion de la rotation évoquée à l’étape 8, puisque le serveur sait immédiatement quelle clé utiliser pour vérifier sans avoir à tester plusieurs candidats.

Deuxièmement, pour les API à très fort trafic, signez uniquement un condensé du corps (son propre hachage SHA-256) plutôt que le corps entier lorsque les payloads sont volumineux : cela réduit la charge CPU sans affaiblir la garantie cryptographique, à condition de signer aussi les métadonnées critiques (méthode HTTP, chemin, timestamp) pour éviter qu’un attaquant ne rejoue le même corps sur une route différente.

Troisièmement, si votre organisation doit se conformer à des exigences réglementaires strictes (secteur financier, infrastructures critiques), envisagez de combiner HMAC avec du TLS mutuel (mTLS) pour ajouter une couche d’authentification au niveau transport, en plus de l’authentification applicative que HMAC fournit déjà. Les deux mécanismes sont complémentaires, pas redondants : mTLS authentifie la connexion, HMAC authentifie chaque message individuellement, ce qui reste utile même si la connexion TLS est terminée par un load balancer intermédiaire.

HMAC dans les autres langages : référence rapide

Les exemples de ce tutoriel utilisent Node.js et Python, mais le même schéma (calculer un HMAC-SHA256 du corps brut, comparer à temps constant) se transpose directement dans n’importe quel langage disposant d’une bibliothèque cryptographique standard. Voici où trouver l’équivalent dans les stacks les plus courantes en entreprise.

LangageFonction de signatureFonction de comparaison sûre
Node.jscrypto.createHmac('sha256', key)crypto.timingSafeEqual()
Pythonhmac.new(key, msg, hashlib.sha256)hmac.compare_digest()
Gohmac.New(sha256.New, key)hmac.Equal()
JavaMac.getInstance("HmacSHA256")MessageDigest.isEqual()
PHPhash_hmac('sha256', $data, $key)hash_equals()
RubyOpenSSL::HMAC.hexdigest('SHA256', key, data)ActiveSupport::SecurityUtils.secure_compare()

Le détail à vérifier systématiquement en changeant de langage : la fonction de comparaison “sûre” existe presque toujours dans la bibliothèque standard ou une bibliothèque de sécurité largement adoptée du langage. Si vous ne la trouvez pas nativement, c’est probablement le signe qu’il faut chercher plus loin plutôt que d’écrire votre propre comparaison, pour les raisons expliquées à l’étape 5.

Le projet complet : structure finale

À l’issue de ce tutoriel, votre projet doit ressembler à ceci, avec un serveur qui vérifie les signatures, un client qui les génère, et une suite de tests qui couvre les cas critiques.

hmac-api-demo/
├── server/
│   ├── index.js          # serveur Express avec vérification HMAC + timestamp
│   ├── index.test.js      # tests unitaires (node --test)
│   ├── .env               # HMAC_SECRET_KEY (jamais commité)
│   └── package.json
└── client/
    ├── send_order.py      # client Python qui signe et envoie les requêtes
    └── venv/

Lancez le serveur avec node server/index.js, puis exécutez le script client avec python client/send_order.py. Vous devriez obtenir une réponse HTTP 200 avec le détail de la commande. Modifiez volontairement un caractère du payload côté client sans recalculer la signature pour observer le rejet HTTP 401, cela confirme que le mécanisme fonctionne dans les deux sens.

Questions fréquentes

HMAC-SHA256 est-il toujours considéré sûr en 2026 ?
Oui. Aucune faiblesse pratique n’a été démontrée contre HMAC-SHA256 lorsqu’il est implémenté correctement avec une clé de taille suffisante (32 octets minimum) et une comparaison à temps constant. C’est toujours l’algorithme recommandé par le NIST via FIPS 198-1 pour l’authentification de messages.

Faut-il utiliser HMAC ou JWT pour authentifier une API ?
Les deux ne s’opposent pas forcément : un JWT signé en HS256 utilise HMAC en interne. La différence est l’usage : JWT encode des données (claims) directement dans le jeton lui-même, alors que HMAC “brut” signe simplement un message externe (le corps d’une requête). Pour signer des requêtes API sortantes ou des webhooks, HMAC brut est plus simple et plus léger.

Peut-on utiliser HMAC pour hacher des mots de passe ?
Non. HMAC est conçu pour l’authentification de messages, pas pour le stockage de mots de passe. Utilisez une fonction dédiée comme Argon2 ou bcrypt, qui ajoutent volontairement du coût de calcul pour ralentir les attaques par force brute, ce que HMAC ne fait pas.

Quelle taille de clé secrète HMAC est recommandée ?
Au minimum 32 octets (256 bits) générés par un générateur cryptographiquement sûr comme openssl rand -hex 32. Une clé plus courte que la taille de sortie du hachage (32 octets pour SHA-256) réduit la marge de sécurité théorique.

HMAC protège-t-il contre les attaques par rejeu par défaut ?
Non, pas nativement. HMAC garantit l’intégrité et l’authenticité du message signé, mais ne dit rien sur le moment où il a été envoyé. Il faut ajouter explicitement un timestamp ou un nonce dans le message signé, comme montré à l’étape 6 de ce tutoriel.

Quelle est la différence entre HMAC-SHA256 et HMAC-SHA3-256 ?
Les deux offrent un niveau de sécurité comparable, mais reposent sur des constructions internes différentes (Merkle-Damgård pour SHA-256, éponge Keccak pour SHA-3). SHA-3 est parfois préféré pour diversifier les familles d’algorithmes utilisées dans une architecture, notamment en cas de doute futur sur SHA-2. Notre article SHA-256 vs SHA-3 détaille les écarts de performance mesurés entre les deux.

Que faire si la clé secrète HMAC fuite ?
Faites tourner la clé immédiatement en suivant la procédure de double-clé décrite à l’étape 8, révoquez l’ancienne clé dès que possible, auditez les logs pour identifier d’éventuels accès malveillants effectués avec la clé compromise, et informez les intégrateurs concernés si la clé est partagée avec des partenaires externes.

Le client et le serveur doivent-ils utiliser le même langage de programmation ?
Non, et c’est justement l’un des avantages de HMAC : l’algorithme est standardisé par la RFC 2104 et FIPS 198-1, donc un client écrit en Python et un serveur écrit en Node.js, Go ou Java produiront exactement la même signature pour un message et une clé identiques. Le tableau de référence rapide plus haut dans cet article liste les fonctions équivalentes dans les langages les plus courants.

HMAC fonctionne-t-il aussi pour signer des fichiers plutôt que des requêtes HTTP ?
Oui, le principe est identique : vous calculez le HMAC du contenu binaire du fichier avec votre clé secrète, vous transmettez cette signature séparément (dans un fichier .sig ou un en-tête), et le destinataire recalcule le HMAC du fichier reçu pour vérifier qu’il n’a pas été altéré. C’est ce mécanisme qu’utilisent certains outils de distribution de paquets internes pour garantir l’intégrité d’un artefact avant déploiement.

Couverture connexe