Le choix entre HMAC et signature numérique est la décision de conception la plus importante de tout schéma d’authentification de message, et c’est une affaire de confiance, pas de vitesse. Un tag HMAC est calculé et vérifié avec la même clé secrète : quiconque peut vérifier peut donc aussi falsifier. Une signature est produite avec une clé privée et vérifiée avec une clé publique : les vérificateurs ne peuvent donc pas créer de nouveaux messages valides.
Vue d’ensemble
| HMAC (RFC 2104, FIPS 198-1) | Signature numérique | |
|---|---|---|
| Clés | Un seul secret partagé | La clé privée signe, la clé publique vérifie |
| Qui peut créer un tag ou une signature valide | Quiconque détient le secret, y compris chaque vérificateur | Seul le détenteur de la clé privée |
| Non-répudiation (prouvable à des tiers) | Non | Oui, à condition qu’un lien de confiance rattache la clé au signataire |
| Distribution des clés | Le secret doit parvenir confidentiellement à chaque vérificateur | La clé publique peut être publiée (JWKS, certificat, DNS) |
| Taille de la preuve | 32 octets pour HMAC-SHA256 | 64 octets (Ed25519, ECDSA P-256), 256–512 octets (RSA) |
| Vitesse, un cœur d’Apple M4 Max | environ 7 millions de tags par seconde pour des messages de 256 octets (OpenSSL 3.6.5) | environ 60 000 signatures Ed25519 ou 87 000 signatures P-256 par seconde |
| Usages typiques | Webhooks, cookies de session, AWS SigV4, TOTP, JWT HS256, MAC des enregistrements TLS 1.2 | Certificats et handshakes TLS, jetons d’ID OIDC, signature de paquets, Git, clés d’accès |
| Post-quantique | Résistant avec SHA-256 (Grover ne fait que diviser par deux l’effort de recherche de préimage) | Schémas classiques cassés par Shor ; ML-DSA ou équivalent nécessaire |
Débit HMAC mesuré avec openssl speed -hmac sha256 (environ 1,83 Go/s avec des entrées de 256 octets) ; débits de signature mesurés avec openssl speed.
La différence tient au modèle de confiance
Un HMAC est un hachage à clé : H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). Son objectif de sécurité est que personne ne puisse produire un tag valide sans la clé. Cela suffit lorsque exactement deux parties partagent la clé et se font mutuellement confiance pour ne pas falsifier, car un tag valide signifie alors « c’est l’autre partie qui a envoyé ceci ». À partir de trois parties, le modèle s’effondre : n’importe quel vérificateur peut se faire passer pour l’expéditeur auprès des autres, et en cas de litige l’expéditeur peut prétendre que c’est le destinataire qui a produit le tag.
Une signature numérique sépare les capacités. Publier la clé de vérification n’aide personne à signer : un seul émetteur peut donc servir des milliers de vérificateurs, et une signature peut être présentée à un auditeur ou à un tribunal comme preuve d’origine, à condition que la clé ait été liée au signataire par un certificat, un annuaire de clés ou un enregistrement de compte. C’est cette séparation qui explique que les jetons d’identité consommés par de nombreux services (jetons d’ID OpenID Connect, jetons d’accès vérifiés par des serveurs de ressources) utilisent des signatures, et que la fuite de la configuration d’un vérificateur soit sans conséquence avec des signatures, mais catastrophique avec HMAC.
En pratique
Gestion des clés. Les secrets HMAC doivent être générés aléatoirement (au moins 256 bits pour HMAC-SHA256 ; la RFC 7518 §3.2 l’exige pour HS256), transmis de façon confidentielle, stockés par chaque vérificateur et renouvelés partout à la fois, ou avec une période de grâce pendant laquelle deux secrets sont valides. Avec des clés de signature, la moitié privée reste en un seul endroit, idéalement un HSM ou un KMS, et la rotation passe par des identifiants de clé dans un jeu de clés publié.
Performances et taille. Un HMAC, ce sont deux calculs de hachage : des millions par seconde et par cœur, avec un tag de 32 octets. Les signatures coûtent des dizaines de microsecondes et font 64 octets ou plus. Ni l’un ni l’autre n’a d’importance pour la plupart du trafic web ; cela compte pour l’authentification paquet par paquet ou pour une messagerie interne à très fort volume, où HMAC ou un AEAD est la norme.
Pièges. Un HMAC doit être comparé en temps constant, ne doit pas être remplacé par hash(key ‖ message) (construction que casse l’attaque par extension de longueur sur SHA-256) et doit authentifier exactement les octets reçus (le corps brut de la requête, pas un objet JSON resérialisé). Les signatures doivent être vérifiées avec un algorithme que le vérificateur impose lui-même, jamais avec celui que choisit le message.
Le script ci-dessous, exécuté avec Node.js 26.10, authentifie le même corps de webhook des deux façons.
import { createHmac, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
const body = Buffer.from('{"event":"invoice.paid","id":"evt_123"}');
// HMAC: sender and receiver share one secret (in production it comes from a secret store).
const secret = randomBytes(32);
const tag = createHmac("sha256", secret).update(body).digest();
const expected = createHmac("sha256", secret).update(body).digest();
console.log("HMAC-SHA256 tag:", tag.length, "bytes, valid:", timingSafeEqual(tag, expected));
// Signature: sender keeps the private key, receivers hold only the public key.
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
const signature = sign(null, body, privateKey);
console.log("Ed25519 signature:", signature.length, "bytes, valid:", verify(null, body, publicKey, signature));
HMAC-SHA256 tag: 32 bytes, valid: true
Ed25519 signature: 64 bytes, valid: true
Le secret HMAC doit être partagé avec chaque vérificateur ; la clé privée Ed25519 ne quitte jamais l’expéditeur, et seule la clé publique de 32 octets est distribuée.
Webhooks et API en pratique
La plupart des fournisseurs de webhooks utilisent HMAC, car chaque abonné reçoit son propre secret : GitHub envoie X-Hub-Signature-256: sha256= suivi du HMAC-SHA256 du corps en hexadécimal, et Stripe signe un horodatage plus le corps dans son en-tête Stripe-Signature, afin que les destinataires puissent rejeter les messages rejoués. Comme le secret de chaque abonné est unique, le problème à plusieurs parties ne se pose pas. Les webhooks signés, fondés sur HTTP Message Signatures (RFC 9421) ou JWS, apparaissent lorsqu’un expéditeur s’adresse à de nombreux destinataires qui ne devraient pas partager de secret, ou lorsque les destinataires veulent vérifier sans avoir à provisionner de secrets. AWS Signature Version 4 est une signature de requêtes fondée sur HMAC ; OAuth DPoP et Web Bot Auth utilisent des signatures parce que le vérificateur ne doit pas pouvoir se faire passer pour le client.
Quand choisir l’un ou l’autre
- Le même service émet et vérifie (cookies de session, jetons CSRF, JWT internes) : HMAC-SHA256.
- Un expéditeur, un destinataire, secret partagé hors bande (webhooks propres à chaque client) : HMAC avec un horodatage dans les données signées.
- Un émetteur, de nombreux vérificateurs (jetons d’identité, API publiques, applications mobiles qui vérifient des données du serveur) : signatures, avec des clés publiées dans un JWKS.
- Preuves pour des tiers (contrats, journaux d’audit, versions de logiciels) : signatures avec des horodatages de confiance.
- Clients qui ne doivent pas pouvoir se faire passer les uns pour les autres (DPoP, authentification des bots, attestation d’appareil) : signatures avec des clés propres à chaque client.
- Débits de messages très élevés sur une liaison de confiance : HMAC ou un AEAD.
Notes de migration
Le passage de HMAC aux signatures intervient généralement quand un deuxième vérificateur apparaît ou qu’un secret fuit.
- Introduisez un identifiant d’algorithme si votre format n’en a pas (
algde JOSE, paramètrealgd’une signature HTTP, préfixe de version), pour que les vérificateurs sachent quel schéma un message utilise sans s’y fier aveuglément. - Publiez la clé publique avec un identifiant de clé (JWKS, annuaire de clés pour signatures HTTP) et laissez aux vérificateurs le temps de la récupérer.
- Signez des deux façons pendant la transition : envoyez à la fois l’en-tête HMAC et la signature, et laissez les vérificateurs privilégier la signature.
- Retirez le secret partagé des vérificateurs qui n’ont besoin que de vérifier ; c’est là que réside le gain de sécurité, ne le conservez donc pas « au cas où ».
- Ne laissez jamais une même clé servir aux deux schémas. Une clé publique RSA réutilisée comme secret HMAC, c’est exactement la confusion d’algorithme qui a cassé des bibliothèques JWT ; consultez HS256 vs RS256.
Références
- RFC 2104, HMAC
- FIPS 198-1, code d’authentification de message à clé fondé sur le hachage
- FIPS 186-5, norme de signature numérique
- RFC 7518 §3.2, HMAC avec SHA-2 dans JWS
- RFC 9421, HTTP Message Signatures
- GitHub : valider les livraisons de webhooks
- Stripe : gérer les points de terminaison de webhooks et la vérification des signatures