Hash, MAC/HMAC et signature numérique sont trois primitives cryptographiques régulièrement confondues par les développeurs, et même par certains architectes sécurité expérimentés. Les trois produisent "une petite valeur qui prouve quelque chose sur un message", mais les garanties sont très différentes : un hash ne prouve que l'intégrité face à des erreurs aléatoires, un HMAC ajoute l'authenticité entre parties qui partagent un secret, une signature ajoute la non-répudiation vers un tiers. Ce guide clarifie, avec arbre de décision, cas d'usage réels et pièges fréquents.
1. Vue d'ensemble - matrice des propriétés
1.1 Les 3 primitives, les 4 propriétés
| Primitive | Intégrité | Authenticité | Non-répudiation | Clé |
|---|---|---|---|---|
| Hash (SHA-256) | Oui (contre erreurs) | Non | Non | Aucune |
| HMAC (HMAC-SHA-256) | Oui (même contre attaquant) | Oui (au détenteur du secret) | Non | Secret partagé |
| Signature (Ed25519) | Oui | Oui | Oui | Clé privée, vérification avec clé publique |
1.2 Question de base à se poser
"Pour me convaincre que ce message n'a pas été modifié et vient bien d'Alice, de quoi est-ce que j'ai besoin ?"
- Juste détecter une corruption réseau : hash suffit.
- Prouver que le message vient d'Alice, et Alice et moi partageons un secret : HMAC.
- Prouver à un tiers qu'Alice a bien émis ce message, sans partager de secret avec lui : signature.
1.3 Conséquence directe
Ces trois primitives ne sont pas interchangeables. Utiliser un hash là où un HMAC est requis = vulnérabilité. Utiliser un HMAC là où une signature est requise = impossibilité de prouver l'origine à un tiers.
2. Hash functions - intégrité sans secret
2.1 Définition
Une fonction de hachage H prend un message de taille arbitraire et produit une empreinte (digest) de taille fixe.
H("Hello, World!") = dffd6021bb2bd5b0af676290809ec3a5...
H("Hello, World?") = 8c9d5dd7e53f3a3e9c7a78a2aeeae3d1...
Un bit de différence en entrée → moitié des bits différents en sortie (effet avalanche).
2.2 Propriétés attendues d'une hash cryptographique
Trois propriétés formelles :
- Preimage resistance : étant donné
h, impossible de trouvermtel queH(m) = h. - Second preimage resistance : étant donné
metH(m), impossible de trouverm' ≠ mtel queH(m') = H(m). - Collision resistance : impossible de trouver deux messages
m1, m2tels queH(m1) = H(m2).
"Impossible" signifie ici computationnellement infaisable avec les ressources actuelles et futures raisonnables.
2.3 Les familles principales en 2026
| Famille | Sortie standard | Statut |
|---|---|---|
| MD5 | 128 bits | Cassée (collisions en secondes). Usage crypto interdit. |
| SHA-1 | 160 bits | Cassée (SHAttered 2017). Déconseillée fortement. |
| SHA-2 (SHA-256, SHA-512) | 256 / 512 bits | Sûre, standard le plus adopté. |
| SHA-3 (Keccak) | 256 / 512 bits | Sûre. Design très différent de SHA-2. |
| BLAKE2 (BLAKE2b, BLAKE2s) | 256 / 512 bits | Sûre, plus rapide que SHA-2. Utilisée par WireGuard, Argon2. |
| BLAKE3 | Variable | Moderne, parallélisable, très rapide. |
Par défaut 2026 :
- SHA-256 si interop / standard.
- SHA-3 / BLAKE2 / BLAKE3 si performance ou indépendance structurelle importante.
2.4 Ce qu'un hash N'EST PAS
Un hash SEUL ne garantit pas l'authenticité. Exemple :
- Alice publie un fichier
archive.zipavec son hash SHA-256. - L'attaquant intercepte, remplace archive.zip par sa version malveillante, et recalcule le hash SHA-256 (la fonction est publique, pas de secret).
- Bob télécharge, compare : le hash matche. Il est piégé.
Hash prouve qu'un message n'a pas été corrompu accidentellement (bit flip réseau, erreur disque), pas qu'il n'a pas été intentionnellement modifié par un attaquant qui contrôle le canal.
2.5 Usages légitimes de hash sans secret
- Fingerprint de fichier pour dedup : Git utilise SHA-1 (historique) / SHA-256 pour identifier les objets.
- Content-addressable storage : IPFS, Sigstore Rekor, Docker image layers.
- Checksum pour intégrité non adversariale : vérifier qu'un download n'est pas corrompu.
- Dedup et index : bases de données, cache keys.
- Merkle trees : Git, blockchain, certificate transparency.
2.6 Password hashing - cas particulier
Ne jamais utiliser SHA-256 directement pour stocker des mots de passe. Un attaquant qui vole la base peut tester des milliards de candidats par seconde sur GPU.
Pour mots de passe : KDFs lents, paramétrables :
- Argon2id (recommandé OWASP 2024).
- bcrypt.
- scrypt.
- PBKDF2 (600 000 iterations min pour SHA-256).
Ces fonctions sont volontairement coûteuses (10-100 ms par calcul) pour rendre le bruteforce non viable.
3. HMAC - intégrité + authenticité avec secret partagé
3.1 Définition
Un MAC (Message Authentication Code) prend un message m et une clé secrète K, et produit un tag :
tag = MAC(K, m)
Le récepteur, qui possède aussi K, recalcule MAC(K, m) et compare. Si égal, le message est authentique (provient d'un détenteur de K) et intègre (n'a pas été modifié).
3.2 HMAC - construction standard
HMAC (Hash-based MAC) construit un MAC à partir d'une hash function de façon prouvablement sûre (Bellare, Canetti, Krawczyk 1996, RFC 2104).
Formule simplifiée :
HMAC(K, m) = H((K XOR opad) || H((K XOR ipad) || m))
Où ipad et opad sont des constantes, H est SHA-256 ou équivalent.
Choix standard 2026 : HMAC-SHA-256 (HS256) ou HMAC-SHA-512.
3.3 Propriétés HMAC
- Intégrité : modification du message = tag incompatible.
- Authenticité : seul un détenteur de
Kpeut produire un tag valide. - PAS de non-répudiation : Alice et Bob partagent
K, donc Alice peut prétendre que c'est Bob qui a émis le message, et réciproquement. Un tiers ne peut pas départager.
3.4 Usages légitimes de HMAC
- Signature de webhook : Stripe, GitHub, Slack signent leurs webhooks sortants avec HMAC-SHA-256 et un secret partagé avec le consommateur. Exemple Stripe : header
Stripe-Signature: t=timestamp,v1=HMAC-SHA-256(secret, timestamp+payload). - Cookies de session signés : Django, Flask-Login signent les cookies côté serveur avec HMAC pour prévenir tampering.
- JWT HS256 : variante JWT avec HMAC-SHA-256 pour signature (voir JWT risques).
- API requests signing : AWS SigV4 utilise HMAC-SHA-256 pour signer les requêtes API.
- Validation d'intégrité inter-services dans environnement partageant un secret.
3.5 HMAC vs autres MACs
- CMAC : MAC basé sur un block cipher (AES-CMAC). OK mais moins courant.
- Poly1305 : MAC extrêmement rapide, utilisé dans ChaCha20-Poly1305 et Wireguard. Spécifique (nonce unique requis).
- KMAC : MAC basé sur SHA-3 / Keccak.
Pour usage général, HMAC-SHA-256 est le choix par défaut, universellement supporté.
3.6 Le piège de la comparaison
Comparer un tag reçu avec le tag attendu doit se faire en constant-time. Une comparaison naïve (memcmp, == en JS) s'arrête au premier byte différent, créant une fuite temporelle qui permet de bruteforcer byte par byte.
Mauvais :
if received_tag == computed_tag: # PIEGE
...Bon :
import hmac
if hmac.compare_digest(received_tag, computed_tag): # constant-time
...Toutes les bibliothèques crypto fournissent une fonction constant-time. Toujours l'utiliser.
4. Signatures numériques - intégrité + authenticité + non-répudiation
4.1 Définition
Dans une signature numérique, Alice possède :
- Une clé privée
skgardée secrète. - Une clé publique
pkpartageable librement.
Alice signe un message m :
signature = Sign(sk, m)
Quiconque possède pk peut vérifier :
Verify(pk, m, signature) -> true / false
4.2 Propriétés
- Intégrité : modification du message = signature invalide.
- Authenticité : seul le détenteur de
skpeut produire une signature valide. - Non-répudiation : comme
skest secret à Alice, elle ne peut nier avoir signé. Un tiers, avecpk, peut prouver que c'est bien Alice.
4.3 Algorithmes standard 2026
| Algorithme | Base | Usage |
|---|---|---|
| Ed25519 (EdDSA) | Courbes Edwards | Recommandation 2026. Rapide, robuste, signature 64 octets. |
| ECDSA P-256 | Courbes NIST | Standard FIPS. Prudence sur implémentation (nonce k unique). |
| ECDSA P-384 / P-521 | Courbes NIST | Hauts niveaux de sécurité. |
| RSA-PSS | RSA | Interop / legacy. Préférer à RSA PKCS#1 v1.5. |
| RSA PKCS#1 v1.5 | RSA | Historique (JWT RS256). Nombreuses attaques, à éviter en nouveaux projets. |
| ML-DSA (Dilithium) | Lattice PQ | Post-quantum, NIST FIPS 204 (2024). |
Par défaut 2026 : Ed25519 pour nouveaux projets. ES256 (ECDSA P-256) ou RS256 (RSA) selon exigences d'interop.
4.4 Usages légitimes de signatures
- Certificats X.509 TLS : CA signe le certificat du domaine. Autorité → domaine → client navigateur qui vérifie.
- Code signing : Microsoft Authenticode (binaires Windows), Apple codesign (apps macOS/iOS), Cosign (images Docker, artefacts).
- Git commits signés : GPG ou SSH signing attribue un commit à une identité cryptographique.
- SSH keys : authentification serveur et client.
- JWT RS256, ES256, EdDSA : tokens émis par un IdP, vérifiés par des services downstream via clé publique.
- Blockchain : chaque transaction signée par la clé privée du wallet.
- Documents électroniques eIDAS : signatures qualifiées avec valeur juridique équivalente à la signature manuscrite.
4.5 Cas où signature est NÉCESSAIRE (HMAC ne suffit pas)
- Vous voulez qu'un tiers (auditeur, client, juge) puisse vérifier l'authenticité sans recevoir votre secret.
- Vous voulez pouvoir prouver juridiquement qu'une personne donnée a émis un document.
- Vous distribuez des artefacts publiquement (images Docker, binaires, packages) et quiconque doit pouvoir vérifier.
Dans ces cas, HMAC est structurellement inadapté.
4.6 Cas où HMAC suffit (signature overkill)
- Communication entre services qui partagent déjà un secret (inter-service dans même organisation).
- Webhook entre deux partenaires qui ont échangé un secret.
- Cookie de session signé qui n'est vérifié que par le serveur émetteur.
Dans ces cas, HMAC est plus simple, plus rapide, et suffisant.
5. Arbre de décision
Face à un besoin, suivre cet arbre :
Besoin de détecter intégrité ?
├─ Non (besoin autre, ex. dedup) → fonction non-crypto
└─ Oui → lire la suite
Contre qui ?
├─ Erreurs aléatoires (réseau, disque) → hash suffit (SHA-256, BLAKE2)
└─ Attaquant → lire la suite
Qui va vérifier ?
├─ Seul l'émetteur (ou un groupe partageant un secret)
│ └─ HMAC-SHA-256 (ou HMAC-SHA-512)
└─ Un tiers qui n'a pas le secret
├─ Signature → lire la suite
│
└─ Choix de l'algorithme ?
├─ Nouveau projet → Ed25519
├─ Interop NIST required → ECDSA P-256 (ES256)
├─ Legacy JWT / interop large → RSA (RS256 ou PS256)
└─ Post-quantum requirement → ML-DSA (Dilithium, FIPS 204)
6. Tableau comparatif synthétique
| Aspect | Hash | HMAC | Signature |
|---|---|---|---|
| Clé requise | Aucune | Secret partagé | Paire privée/publique |
| Distribution clé | N/A | Out-of-band avant usage | Clé publique partageable librement |
| Vitesse (typical) | Ultra rapide (GB/s) | Rapide (GB/s) | Plus lent (ms par signature/vérif) |
| Taille sortie | 256-512 bits | 256-512 bits | 256-512 octets selon algo |
| Intégrité | Oui (contre erreurs) | Oui (même vs attaquant) | Oui |
| Authenticité | Non | Oui (au groupe qui partage K) | Oui (au détenteur de sk) |
| Non-répudiation | Non | Non | Oui |
| Vérifiable par tiers | N/A | Si tiers a le secret | Oui sans secret |
| Standard recommandé 2026 | SHA-256, BLAKE2 | HMAC-SHA-256 | Ed25519 |
| Bibliothèque type | hashlib.sha256 | hmac.new | PyNaCl / cryptography |
7. Erreurs classiques
7.1 Utiliser un hash pour authentifier
Classique : un service distribue des webhooks avec X-Signature: SHA-256(payload) et pense que c'est signé. Faux. N'importe qui peut recalculer. Il faut HMAC-SHA-256(secret, payload).
7.2 Comparer les tags en non constant-time
Vu §3.6. Le plus fréquent dans le code custom.
7.3 Utiliser SHA pour stocker des passwords
Sur GPU moderne, des milliards de SHA par seconde. Base fuitée = passwords cassés en heures. Utiliser Argon2id.
7.4 HMAC avec une clé trop courte
HMAC-SHA-256 avec une clé de 8 bytes = bruteforce possible. Minimum 32 bytes aléatoires (256 bits d'entropie).
7.5 Clé HMAC connue de trop de parties
Si 50 équipes différentes partagent le même secret HMAC pour signer leurs messages inter-services, il devient impossible d'attribuer une signature à une équipe spécifique → plus vraiment d'authenticité utile. Mitigation : une paire de secrets par paire de services, ou passer aux signatures.
7.6 RSA PKCS#1 v1.5 signature parsing
Bug Bleichenbacher et variantes récurrentes. Ne pas utiliser PKCS#1 v1.5 pour nouveaux systèmes. Utiliser PSS.
7.7 Signature sans timestamp ni expiration
Une signature sans contexte temporel est rejouable à vie. Inclure un timestamp ou nonce dans le message signé (comme Stripe avec t=timestamp dans la signature du webhook).
7.8 Confondre hash de fichier et signature d'artefact
Publier un fichier avec son hash SHA-256 sur le même site ne signe rien : attaquant qui compromet le site change les deux. Il faut soit un canal séparé (autre domaine, DNSSEC, Certificate Transparency), soit une signature vérifiable avec une clé publique établie préalablement.
8. Cas avancés - keyed hash et constructions modernes
8.1 Keyed BLAKE2
BLAKE2 supporte nativement une clé optionnelle, ce qui en fait à la fois une hash et un MAC :
import hashlib
tag = hashlib.blake2b(message, key=secret_key, digest_size=32).digest()Plus rapide que HMAC-SHA-256, pas de construction "HMAC around".
8.2 KMAC (Keccak MAC)
MAC basé sur SHA-3. Alternative moderne à HMAC, conçue spécifiquement pour Keccak.
8.3 Signatures avancées
Cas de niche mais utiles dans certains contextes :
- Ring signatures : prouve qu'un signataire est dans un groupe sans révéler lequel.
- Blind signatures : signer un message sans voir son contenu (utilisé par certains cash systems).
- Threshold signatures : une signature valide nécessite un quorum de signataires.
- Aggregate signatures : combiner N signatures en une seule plus compacte.
Usages : crypto-monnaies, e-voting, anonymat. Hors scope pour la plupart des applications.
8.4 Post-quantum signatures
Le NIST a publié en 2024 :
- ML-DSA (Dilithium, FIPS 204) : standard principal.
- SLH-DSA (SPHINCS+, FIPS 205) : alternative hash-based.
Signatures PQ plus grandes que ECDSA/Ed25519 (1-4 KB vs 64 bytes), plus lentes. Déploiement en hybride (Ed25519 + Dilithium) pour la transition.
9. Implémentation pratique par cas d'usage
9.1 Vérifier un webhook Stripe
import hmac
import hashlib
import time
def verify_stripe_webhook(payload: bytes, sig_header: str, secret: bytes) -> bool:
# Parser Stripe-Signature: t=1720000000,v1=abc123...
parts = dict(item.split("=") for item in sig_header.split(","))
timestamp = parts["t"]
signature = parts["v1"]
# Rejeter si timestamp trop ancien (anti-replay)
if abs(time.time() - int(timestamp)) > 300:
return False
# Recalculer HMAC
signed_payload = f"{timestamp}.".encode() + payload
expected = hmac.new(secret, signed_payload, hashlib.sha256).hexdigest()
# Comparaison constant-time
return hmac.compare_digest(expected, signature)9.2 Signer un artefact avec Cosign (keyless)
# Signer une image Docker avec OIDC keyless
cosign sign --yes registry.example.com/app@sha256:abc123
# Vérifier la signature
cosign verify \
--certificate-identity="https://github.com/example/app/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
registry.example.com/app@sha256:abc123Cosign utilise Ed25519 ou ECDSA. Signature enregistrée dans Rekor (transparency log public).
9.3 Fingerprint de contenu (hash)
import hashlib
def content_fingerprint(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()Usage : dedup, cache keys, content-addressable storage. Pas besoin de secret.
9.4 Cookie signé avec HMAC
import hmac
import hashlib
import base64
import json
def sign_cookie(payload: dict, secret: bytes) -> str:
payload_bytes = json.dumps(payload, sort_keys=True).encode()
tag = hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()
return base64.b64encode(payload_bytes).decode() + "." + tag
def verify_cookie(cookie: str, secret: bytes) -> dict | None:
try:
payload_b64, tag = cookie.rsplit(".", 1)
payload_bytes = base64.b64decode(payload_b64)
expected_tag = hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected_tag, tag):
return None
return json.loads(payload_bytes)
except Exception:
return NonePattern utilisé par Django, Flask-Login, Express signed-cookies.
9.5 Vérifier un commit Git signé
# Afficher les signatures des commits
git log --show-signature
# Vérifier un commit spécifique
git verify-commit HEAD
# Exiger les commits signés sur une branche (config)
git config --global commit.gpgsign trueGitHub, GitLab affichent un badge "Verified" pour les commits signés par une clé GPG/SSH liée à un compte.
10. Checklist - choisir la bonne primitive
Pour détecter une corruption aléatoire (réseau, disque)
- Hash (SHA-256, BLAKE2, BLAKE3)
Pour identifier du contenu (dedup, cache, Git, IPFS)
- Hash cryptographique (SHA-256, BLAKE3)
Pour vérifier l'intégrité face à un adversaire, avec secret partagé
- HMAC-SHA-256 ou équivalent
- Clé minimum 256 bits
- Comparaison constant-time
- Rotation périodique de la clé
Pour signer un webhook, un token interne, un cookie
- HMAC-SHA-256 ou équivalent
- Inclure timestamp pour anti-replay
- Secret par service, pas global
Pour stocker des mots de passe
- Argon2id (recommandé)
- bcrypt (acceptable)
- PBKDF2-SHA-256 avec 600000+ iterations
- Jamais SHA-* seul, jamais MD5/SHA-1
Pour signer un artefact distribué publiquement
- Ed25519 ou ECDSA P-256
- Vérification accessible publiquement via clé publique
- Cosign keyless pour containers/artefacts
Pour signer un document juridiquement opposable (eIDAS)
- Certificat qualifié + signature Ed25519 / ECDSA / RSA-PSS via prestataire certifié
- Horodatage qualifié
Pour JWT
- RS256 / ES256 / EdDSA pour interop multi-consumers (signature)
- HS256 pour service mono-consommateur (HMAC)
- Jamais
alg: none, algorithme fixé côté serveur
Pour code signing enterprise
- Microsoft Authenticode (Windows binaries)
- Apple codesign (macOS/iOS apps)
- Cosign (containers, artefacts)
11. Post-quantum - impact différentiel
Grover divise la sécurité symétrique et hash par 2 :
- SHA-256 → 128 bits effectifs contre collision (acceptable).
- SHA-512 → 256 bits (confortable).
- HMAC-SHA-256 avec clé 256 bits → 128 bits effectifs (acceptable).
Shor casse la cryptographie asymétrique classique :
- Ed25519, ECDSA, RSA → cassés.
- ML-DSA (Dilithium) → nouveau standard PQ.
- SLH-DSA (SPHINCS+) → alternative hash-based.
Impact : les hashes et HMAC ont juste besoin de tailles augmentées, les signatures nécessitent un changement complet d'algorithme. D'où l'urgence relative : le symétrique attend, l'asymétrique migre dès maintenant pour le long terme.
12. Verdict et posture Zeroday
La confusion hash / HMAC / signature est la confusion crypto la plus fréquente dans le code en production en 2026. Elle cause des vulnérabilités évitables : webhooks "signés" par hash recalculable, passwords stockés en SHA-256, signatures HMAC quand une vraie signature asymétrique était nécessaire.
Pour un développeur : retenir les trois propriétés (intégrité, authenticité, non-répudiation) et les associer aux trois primitives. Cette grille mentale élimine 80 % des confusions. Pas besoin d'être cryptographe, juste de savoir poser la bonne question avant de coder.
Pour un AppSec : le check systématique à faire dans tout audit crypto - "est-ce que le bon outil est utilisé pour le bon besoin ?" - révèle régulièrement des patterns fautifs même dans du code récent.
Pour une organisation : documenter un guide interne de choix crypto (arbre de décision §5) consultable par tous les développeurs produit des gains durables. Coût faible, impact élevé.
Pour approfondir : qu'est-ce que la cryptographie pour le panorama, chiffrement symétrique expliqué pour les AEAD qui combinent chiffrement et authentification, JWT : risques et bonnes pratiques pour l'application directe HS256/RS256/ES256/EdDSA dans les tokens, gestion de session sécurisée pour les cookies signés HMAC en pratique web.







