Sécurité appliquée · L3 · Section 2/5
Cryptographie de base
Progression
#Cryptographie de base
La cryptographie fournit des garanties mathématiques: confidentialité (chiffrement), intégrité et authenticité (MAC, signatures). Ses outils sont des primitives spécialisées; leur sécurité dépend du schéma qui les compose. Les pièges classiques (ECB, nonce réutilisé, clé dérivée d'un mot de passe) sont toujours des erreurs de composition, pas des failles des primitives.
Prérequis
- Python: manipulation d'octets (
bytes) et d'hexadécimal.- Notion de clé secrète partagée vs paire clé publique/privée.
- Section « Principes et modèle de menace » du même module.
Objectifs d'apprentissage
- Choisir la bonne primitive selon le besoin: hachage, MAC, chiffrement symétrique/asymétrique, signature.
- Expliquer pourquoi un hachage seul n'authentifie pas, et pourquoi ECB et les nonces réutilisés cassent la confidentialité.
- Utiliser une bibliothèque de haut niveau (Fernet, NaCl) plutôt que des primitives brutes.
#La bonne primitive pour le bon besoin
| Besoin | Primitive | Exemple standard | Ce que ça ne fait PAS |
|---|---|---|---|
| Empreinte détectant la modification accidentelle ou comparer des fichiers | Hachage (non clé) | SHA-256, SHA-3, BLAKE2 | Authentifier un message, stocker un mot de passe tel quel |
| Détecter la modification malveillante avec un secret partagé | MAC | HMAC-SHA-256 | Chiffrer, non-répudiation (le vérificateur peut aussi produire le MAC) |
| Confidentialité avec secret partagé | Chiffrement symétrique authentifié (AEAD) | AES-GCM, ChaCha20-Poly1305 (via Fernet, NaCl) | Distribution de la clé, authenticité de l'expéditeur avant échange |
| Confidentialité + échange de clés sans secret préalable | Asymétrique | X25519, RSA-OAEP | Chiffrer de gros volumes (lent; on chiffre une clé de session) |
| Authenticité + non-répudiation avec clé privée | Signature | Ed25519, ECDSA, RSA-PSS | Confidentialité du message |
La colonne de droite est la plus importante: la plupart des incidents viennent d'une primitive utilisée pour un besoin qu'elle ne couvre pas.
#Hachage: qu'est-ce qu'on obtient vraiment
Un hachage cryptographique transforme un message de taille quelconque en empreinte de taille fixe, calcul rapide, mais à sens unique (on ne remonte pas vers le message) et résistant aux collisions (deux messages distincts ont la même empreinte avec une probabilité négligeable).
Deux usages légitimes et un usage fautif:
- Légitime: vérifier l'intégrité d'un téléchargement (l'empreinte est publiée sur un canal de confiance).
- Légitime: dédupliquer des contenus, indexer sans stocker l'original.
- Fautif: stocker des mots de passe avec SHA-256 seul. Un SHA rapide se force par milliards d'essais par seconde sur GPU; il faut un hachage adaptatif salé (Argon2id, bcrypt, scrypt) avec un facteur de travail réglé.
#Démonstration (Python)
Vérification attendue: 'hello' et 'Hello' donnent des empreintes sans aucun rapport (avalanche), et la longueur de sortie est constante (64 caractères hexadécimaux, soit 256 bits).
#MAC: authentifier avec un secret partagé
Un hachage seul ne détecte que les modifications accidentelles: n'importe qui peut recalculer l'empreinte d'un message falsifié. Un MAC (Message Authentication Code) ajoute une clé secrète: sans elle, la falsification devient indétectable.
Deux points à retenir du résultat: la vérification du message légitime réussit, celle du message falsifié échoue; et on utilise hmac.compare_digest plutôt que == pour éviter les attaques par mesures de temps.
#Chiffrement symétrique authentifié: Fernet
En pratique, on ne compose pas soi-même AES + mode + IV + MAC. On utilise une construction de haut niveau qui garantit chiffrement et authenticité ensemble. Fernet (bibliothèque cryptography) encapsule AES-128-CBC avec HMAC-SHA-256 sur les données et un vecteur d'initialisation aléatoire inclus dans le jeton.
L'exercice complet: générer une clé, chiffrer, déchiffrer, et observer les échecs (mauvaise clé, jeton altéré).
1from cryptography.fernet import Fernet2import os3 4def generer_cle():5 """Genere une cle Fernet (32 octets base64)."""6 return Fernet.generate_key()7 8def chiffrer_message(message: str, cle: bytes) -> bytes:9 """Chiffre un message avec la cle donnee. IV aleatoire inclus dans le jeton."""10 return Fernet(cle).encrypt(message.encode())11 12def dechiffrer_message(jeton: bytes, cle: bytes) -> str:13 """Dechiffre et verifie l'authenticite. Leve InvalidToken si echec."""14 return Fernet(cle).decrypt(jeton).decode()Points d'observation: la modification d'un seul octet du jeton provoque un refus (le MAC intégré détecte l'altération), et deux chiffrements du même message produisent des jetons distincts grâce à l'IV aléatoire. C'est précisément ce qui manque aux bricolages maison.
#Ce qui casse en pratique (et pourquoi)
ECB (Electronic Codebook) chiffre chaque bloc indépendamment avec la même clé: deux blocs clairs identiques donnent deux blocs chiffrés identiques, les motifs transparaissent. C'est le mode par défaut le plus dangereusement naïf; ne jamais l'utiliser pour des données structurées.
Nonce/IV réutilisé. En GCM ou CTR, réutiliser un nonce avec la même clé permet de retrouver le XOR de deux clairs, et en GCM cela divulgue la clé d'authentification. Un nonce de compteur strictement croissant, ou 96 bits aléatoires avec une limite d'usage, évite cela.
Clé dérivée d'un mot de passe avec un simple SHA-256: attaquable par dictionnaire sur GPU. Il faut une fonction de dérivation à facteur de travail (Argon2id, scrypt, PBKDF2 avec itérations élevées).
Chiffrement sans authentification (CBC sans MAC): un attaquant peut tronquer ou décaler des blocs (padding oracle). Toujours préférer un mode authentifié (AEAD: GCM, Poly1305) sauf besoin précis et documenté.
#Exercice corrigé: détecter ECB
On chiffre une image fortement redondante (grosse zone uniforme) en ECB et en mode authentifié. L'objectif: observer que ECB « laisse voir » les motifs, pas le mode sûr.
1# Demonstration: ECB laisse voir les motifs, un mode avec IV ne le permet pas.2# On utilise un chiffrement par bloc deterministe comme stand-in d'AES-ECB3# (meme propriete observable: blocs clairs identiques -> chiffres identiques).4 5def xor(a, b):6 return bytes(x ^ y for x, y in zip(a, b))7 8CLE = b"K" * 169 10def chiffre_bloc_deterministe(bloc):11 # rotation d'un octet puis XOR: fonction deterministe non involutive12 return xor(bloc[1:] + bloc[:1], CLE)13 14blocs_clairs = [b"AAAAAAAAAAAAAAAA"] * 6 # zone uniforme de l'imageLa correction s'observe dans les deux booléens: ECB produit des blocs chiffrés identiques pour des blocs clairs identiques; un mode à IV aléatoire ne le permet pas. Transposé à une image, le résultat ECB ressemble à une version bruitée de l'original.