Aller au contenu
ecdsa.com

HMAC vs signatures numériques : secret ou paire de clés

Les deux prouvent qu’un message n’a pas été modifié. Seule une signature prouve qui l’a produit à quelqu’un qui ne peut pas en produire une lui-même.
Éléments comparésHMACSignature numérique

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ésUn seul secret partagéLa clé privée signe, la clé publique vérifie
Qui peut créer un tag ou une signature valideQuiconque détient le secret, y compris chaque vérificateurSeul le détenteur de la clé privée
Non-répudiation (prouvable à des tiers)NonOui, à condition qu’un lien de confiance rattache la clé au signataire
Distribution des clésLe secret doit parvenir confidentiellement à chaque vérificateurLa clé publique peut être publiée (JWKS, certificat, DNS)
Taille de la preuve32 octets pour HMAC-SHA25664 octets (Ed25519, ECDSA P-256), 256–512 octets (RSA)
Vitesse, un cœur d’Apple M4 Maxenviron 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 typiquesWebhooks, cookies de session, AWS SigV4, TOTP, JWT HS256, MAC des enregistrements TLS 1.2Certificats et handshakes TLS, jetons d’ID OIDC, signature de paquets, Git, clés d’accès
Post-quantiqueRé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.

Node.js : tag HMAC-SHA256 et signature Ed25519 d’un même corps de webhook
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));
Sortie
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.

  1. Introduisez un identifiant d’algorithme si votre format n’en a pas (alg de JOSE, paramètre alg d’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.
  2. 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.
  3. 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.
  4. 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ù ».
  5. 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

Tous les comparatifs