Retour au cours

cyber / cybersecurite-fondamentale

Cryptographie : hachage

Leçon 51 exercice

Explication

Ce que vous allez apprendre

  • Expliquer ce qu'est une empreinte cryptographique et pourquoi elle est irréversible par construction
  • Distinguer un hash simple (intégrité) d'un HMAC (intégrité + authenticité)
  • Reconnaître pourquoi MD5 et SHA1 sont obsolètes en contexte de sécurité, mais restent utilisables pour un simple checksum
  • Comparer deux hash en temps constant pour éviter une attaque temporelle
  • Vérifier la signature d'un webhook entrant avec HMAC-SHA256

Dans quel contexte ?

Une API de paiement envoie un webhook à chaque transaction pour notifier le site marchand. Sans vérification, n'importe qui connaissant l'URL du webhook pourrait envoyer une fausse notification de paiement réussi et débloquer une commande gratuitement. La signature HMAC jointe à chaque requête permet au serveur marchand de vérifier, avant de faire confiance au message, qu'il provient bien du fournisseur de paiement et n'a pas été modifié en chemin.

Première étape : imaginer la machine avant la formule. Imaginez une machine qui prend n'importe quel document, même très long, et en ressort toujours une "empreinte" de taille fixe, comme un code-barres unique. C'est exactement ce que fait une fonction de hachage.

Deuxième étape : les deux propriétés qui rendent cette empreinte utile. D'abord, elle est déterministe : la même entrée donne toujours la même sortie. Ensuite, et c'est le point clé, il est impossible de remonter de l'empreinte vers le document original.

Troisième étape : à quoi ça sert concrètement. Le cas d'usage le plus simple est de vérifier qu'un fichier téléchargé n'a pas été corrompu ou altéré. On compare l'empreinte calculée localement à celle publiée par l'éditeur. Si un seul bit change dans le fichier, l'empreinte change complètement, c'est ce qu'on appelle l'effet avalanche.

Il reste un problème : un hash simple ne prouve pas qui l'a créé. N'importe qui peut recalculer un hash et le remplacer par un autre. Un hash garantit donc l'intégrité (le fichier n'a pas changé) mais pas l'authenticité (on ne sait pas qui a produit ce fichier).

Voici comment on résout ce problème : le HMAC. Le HMAC ajoute une clé secrète partagée dans le calcul. Seul quelqu'un qui connaît cette clé peut produire une signature valide. C'est exactement ce qui permet à un serveur de prouver qu'un webhook vient bien du fournisseur attendu, et pas d'un imposteur qui aurait simplement deviné l'URL.

Piège fréquent

Comparer deux hash avec l'opérateur == classique s'arrête dès le premier caractère différent, ce qui prend un temps légèrement variable selon où la différence apparaît. Un attaquant patient peut mesurer ces micro-différences de temps pour deviner le hash caractère par caractère (attaque temporelle). Utilisez toujours une comparaison en temps constant (hmac.compare_digest en Python) pour tout ce qui touche à la sécurité.

Dernier point, pour éviter une confusion avec la leçon précédente sur les mots de passe.

AlgorithmeUsage recommandéUsage à éviter
SHA-256 / SHA-3 / BLAKE2Intégrité de fichiers, HMACHacher un mot de passe seul
MD5 / SHA1Checksum non-sécuritaire (détection de corruption)Tout usage de sécurité (collisions connues)
bcrypt / argon2idStockage de mots de passe (voir leçon précédente)Intégrité de fichiers (trop lent, inutile)

MD5, SHA1 et SHA256 sont parfaits pour l'intégrité de fichiers, mais totalement inadaptés aux mots de passe, car ils sont conçus pour être rapides, exactement l'inverse de ce qu'on veut face à un attaquant qui teste des milliards de combinaisons.

Prérequis

Cette leçon suppose d'avoir déjà vu le hachage de mots de passe (bcrypt/argon2) dans la leçon précédente, pour bien comprendre pourquoi ces deux usages du hachage ne se substituent jamais l'un à l'autre.

Maintenant qu'on sait vérifier qu'une donnée n'a pas changé, la prochaine leçon s'attaque à un autre besoin : la rendre totalement illisible pour qui n'a pas la clé. C'est le rôle du chiffrement.

Commandes & code

Cryptographie : fonctions de hachage

Une fonction de hachage transforme une donnée de taille arbitraire en une empreinte de taille fixe, de manière déterministe et (idéalement) irréversible.

text
# Propriétés attendues d'une fonction de hachage cryptographique
1. Déterministe       : même entrée -> toujours la même sortie
2. Rapide à calculer   (pour l'intégrité, PAS pour les mots de passe)
3. Effet avalanche     : un bit d'entrée changé -> sortie totalement différente
4. Résistance aux collisions : difficile de trouver deux entrées avec le même hash
5. Irréversibilité     : impossible de retrouver l'entrée à partir de la sortie
python
import hashlib

data = b"Technologik e-learning platform"

print(hashlib.sha256(data).hexdigest())
print(hashlib.sha3_256(data).hexdigest())     # SHA-3, famille Keccak
print(hashlib.blake2b(data).hexdigest())      # BLAKE2, très performant

# MD5 et SHA1 sont OBSOLÈTES pour la sécurité (collisions démontrées) : n'utiliser que pour
# des checksums non-sécuritaires (ex: détection de corruption de fichier, PAS d'intégrité adverse)
print(hashlib.md5(data).hexdigest())   # à éviter en contexte sécurité
bash
# Vérifier l'intégrité d'un fichier téléchargé (checksum)
sha256sum technologik-release-v2.3.0.tar.gz
# Comparer avec la valeur publiée par l'éditeur (souvent dans un fichier .sha256 signé)

echo "b2c4e1... technologik-release-v2.3.0.tar.gz" | sha256sum -c -
python
# HMAC (Hash-based Message Authentication Code) : hachage AVEC une clé secrète
# Garantit l'authenticité ET l'intégrité (pas seulement l'intégrité comme un hash simple)
import hmac
import hashlib

secret_key = b"clé-secrète-partagée-longue-et-aleatoire"
message = b'{"amount": 100, "currency": "EUR"}'

signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()

def verify_hmac(message: bytes, signature: str, key: bytes) -> bool:
    expected = hmac.new(key, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)  # comparaison en temps constant, anti-timing-attack

assert verify_hmac(message, signature, secret_key) is True
python
# Utilisation typique : signer un webhook pour prouver qu'il vient bien de l'émetteur attendu
# (ex: Stripe, GitHub envoient un header X-Hub-Signature-256 = HMAC du corps de la requête)
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib

WEBHOOK_SECRET = b"secret-partage-avec-le-fournisseur"

@app.post("/webhooks/payment")
async def handle_webhook(request: Request):
    body = await request.body()
    signature_header = request.headers.get("X-Signature-256", "")
    expected = "sha256=" + hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature_header):
        raise HTTPException(status_code=401, detail="Signature invalide")
    # ... traiter l'événement en confiance ...
text
# Pièges classiques à éviter
- Comparer deux hash avec "==" au lieu de hmac.compare_digest -> vulnérable aux timing attacks
- Utiliser un hash rapide (sha256) pour des mots de passe -> utiliser bcrypt/argon2 (voir leçon dédiée)
- Réutiliser la même clé HMAC pour plusieurs usages différents -> dériver des clés distinctes (HKDF)
- Oublier de versionner l'algorithme utilisé -> stocker "argon2id$v=19$..." pas juste le hash brut

Résumé

  • Un hash cryptographique vérifie l'intégrité ; un HMAC vérifie en plus l'authenticité (clé secrète).
  • SHA-256/SHA-3/BLAKE2 pour l'intégrité, jamais MD5/SHA1 en contexte adversarial.
  • hmac.compare_digest (temps constant) au lieu de == pour comparer des signatures.
  • Les hash sont rapides par design : inadaptés seuls pour protéger des mots de passe.

Exercices pratiques

1 disponible
1

Mission : sécuriser le webhook de paiement de Technologik

Objectif : Corriger une vérification de webhook vulnérable et raisonner sur les attaques temporelles contre la comparaison de signatures.

Contexte

Le fournisseur de paiement de Technologik envoie un webhook signé à chaque transaction, avec un en-tête X-Signature-256. Le code actuel du endpoint compare la signature reçue à la signature attendue avec l'opérateur == classique de Python, directement sur des chaînes de caractères.

Résoudre l’exercice →