Aller au contenu

Transformer un bloc en chiffrement de message

Un chiffrement par bloc comme l’AES (Advanced Encryption Standard) ne sait faire qu’une chose : transformer 16 octets en 16 autres octets sous une clé. Un fichier, un paquet réseau ou un secteur de disque en comptent bien davantage. Le mode opératoire est la règle qui enchaîne les blocs, et c’est lui, bien plus que l’algorithme, qui décide de ce qui fuit. Cette fiche passe en revue les modes historiques, leurs paramètres et leurs fuites, le cas particulier du chiffrement de disque, puis la sortie vers les modes authentifiés.

→ Lire l’explication complète de la marmotte

Un chiffrement par bloc est une permutation paramétrée par la clé : pour une clé donnée, chaque bloc clair possible correspond à exactement un bloc chiffré. Appliquée telle quelle, cette propriété devient un défaut : le même bloc clair donne toujours le même bloc chiffré. Un mode doit donc résoudre trois problèmes à la fois. Le découpage d’abord, puisque le message ne tombe presque jamais sur un multiple de 16 octets. Le chaînage ensuite, pour que deux blocs identiques à des positions différentes, ou dans deux messages différents, ne produisent pas le même résultat. La fuite de structure enfin, qui est précisément ce qu’un chaînage raté laisse passer : répétitions, longueurs, positions des modifications.

Pour casser le déterminisme, presque tous les modes introduisent un paramètre supplémentaire : un IV (Initialization Vector, vecteur d’initialisation), un nonce (valeur à usage unique) ou un compteur. Ces paramètres sont publics : ils voyagent en clair à côté du chiffré, le plus souvent en tête. Leur secret n’apporte rien ; leurs propriétés, en revanche, sont vitales. Selon le mode, il faut qu’ils soient imprévisibles, ou seulement uniques, et la différence entre les deux occupe une bonne part des incidents de ce domaine.

Les cinq modes classiques, ECB (Electronic Codebook), CBC (Cipher Block Chaining), CFB (Cipher Feedback), OFB (Output Feedback) et CTR (Counter), sont décrits dans la publication NIST SP 800-38A (National Institute of Standards and Technology, 2001). Le tableau résume leurs propriétés avant le détail.

ModeParamètreExigenceRemplissageParallélisable
ECBaucunsans objetouichiffrement et déchiffrement
CBCIVimprévisibleouidéchiffrement seulement
CFBIVimprévisiblenondéchiffrement seulement
OFBIVuniquenonnon (flot précalculable)
CTRnonce + compteuruniquenonchiffrement et déchiffrement

L’ECB chiffre chaque bloc indépendamment : C_i = E_K(P_i). Il n’a ni IV ni chaînage, et il livre donc l’égalité des blocs à quiconque regarde. La démonstration tient en une ligne : 32 octets identiques produisent deux blocs chiffrés identiques.

Fenêtre de terminal
printf 'A%.0s' {1..32} | openssl enc -aes-128-ecb -nopad \
-K 000102030405060708090a0b0c0d0e0f | xxd

Les deux lignes affichées par xxd sont rigoureusement les mêmes. L’atelier classique applique ECB au corps d’une image bitmap non compressée en conservant l’en-tête : le dessin reste parfaitement lisible, simplement repeint en couleurs aléatoires.

Le CBC mélange chaque bloc clair avec le bloc chiffré précédent par un XOR (OU exclusif) avant de le chiffrer : C_i = E_K(P_i ⊕ C_(i-1)), avec C_0 = IV. Le déchiffrement inverse l’opération : P_i = D_K(C_i) ⊕ C_(i-1). Le chiffrement est séquentiel, mais le déchiffrement se parallélise puisque tous les C_i sont connus d’avance.

L’IV de CBC doit être imprévisible par l’attaquant au moment où il choisit son clair, pas seulement unique. TLS (Transport Layer Security) 1.0 réutilisait le dernier bloc chiffré de l’enregistrement précédent comme IV du suivant ; l’attaque BEAST (Browser Exploit Against SSL/TLS, 2011, SSL pour Secure Sockets Layer) a exploité cette prévisibilité pour retrouver des cookies.

Comme CBC traite des blocs entiers, le dernier doit être complété. Le remplissage le plus courant, dit PKCS#7 (Public-Key Cryptography Standards n° 7), ajoute n octets valant chacun n : il manque 3 octets, on ajoute 03 03 03 ; le message tombe juste, on ajoute un bloc entier de 10 (16 en hexadécimal). Le déchiffreur lit le dernier octet, vérifie la séquence, puis la retire. C’est cette vérification qui ouvre la faille la plus célèbre des modes non authentifiés.

Le CFB chiffre le bloc chiffré précédent et le combine par XOR au clair : C_i = P_i ⊕ E_K(C_(i-1)). Il est autosynchronisant : après la perte ou la corruption d’un bloc, le déchiffrement retombe sur ses pieds dès que le bloc fautif sort de la fenêtre de rétroaction (en CFB à bloc entier, une erreur abîme le bloc courant et le suivant, pas au-delà).

L’OFB rechiffre sa propre sortie, indépendamment du message : O_i = E_K(O_(i-1)), C_i = P_i ⊕ O_i. C’est un mode synchrone : le flot de clé ne dépend que de la clé et de l’IV, il peut être calculé à l’avance, et un bit inversé dans le chiffré inverse exactement le même bit dans le clair, sans propagation. Pratique contre le bruit, désastreux contre un attaquant qui sait où se trouve le montant d’un virement.

Le CTR chiffre des valeurs de compteur successives et combine le résultat au clair : C_i = P_i ⊕ E_K(nonce ‖ i). Chaque bloc est indépendant, ce qui permet le parallélisme, l’accès direct au milieu d’un fichier et le traitement des blocs dans n’importe quel ordre. Aucun remplissage n’est nécessaire : on tronque le dernier bloc de flot.

L’exigence porte sur l’unicité du couple clé et compteur, jamais sur son imprévisibilité. Si deux messages sont chiffrés avec le même nonce et la même clé, le même flot les recouvre, et C1 ⊕ C2 = P1 ⊕ P2 : le flot s’annule, il reste la combinaison des deux clairs, qu’une connaissance partielle de l’un suffit à démêler. Aucune erreur n’est levée.

Aucun de ces modes ne protège l’intégrité. En CBC, modifier un bit de C_(i-1) inverse le même bit de P_i et transforme P_(i-1) en bouillie ; en CTR et OFB, l’inversion est chirurgicale. Des blocs ECB peuvent être permutés, dupliqués ou retirés ; un message CBC entier peut être rejoué tel quel. Le déchiffreur rend toujours quelque chose, que l’application prend pour la volonté de l’expéditeur.

L’oracle de remplissage, construit puis corrigé

Section intitulée « L’oracle de remplissage, construit puis corrigé »

L’atelier le plus instructif consiste à construire un oracle jouet. Un service reçoit IV ‖ C, déchiffre en CBC, vérifie le remplissage PKCS#7, et répond 400 padding invalide ou 403 accès refusé. Ces deux réponses distinctes suffisent.

Pour retrouver le dernier octet de P_1, l’attaquant modifie le dernier octet de l’IV, essaie les 256 valeurs possibles et soumet chaque variante. Quand le service répond « accès refusé » plutôt que « padding invalide », le dernier octet déchiffré vaut très probablement 01. Il en déduit D_K(C_1)[15] = IV'[15] ⊕ 01, puis P_1[15] = D_K(C_1)[15] ⊕ IV[15]. Il vise ensuite 02 02, puis 03 03 03, et ainsi de suite : au plus 256 requêtes par octet, donc au plus 4 096 pour un bloc de 16 octets et environ la moitié en moyenne, et la clé n’a jamais été touchée. Vaudenay a décrit l’attaque en 2002 ; elle est détaillée dans la brèche attaque par oracle de remplissage CBC.

La correction naïve, unifier le message d’erreur, ne suffit pas : une différence de temps de traitement fait le même office, comme l’a montré Lucky Thirteen (2013) contre TLS en CBC. La correction réelle consiste à authentifier le chiffré avant de le déchiffrer : calculer un HMAC (Hash-based Message Authentication Code) sur IV ‖ C, le vérifier en temps constant, et rejeter sans autre traitement si la vérification échoue. Le remplissage n’est alors examiné que sur un chiffré déjà prouvé authentique, et l’oracle se tait faute de questions recevables.

Un disque impose des contraintes qu’aucun mode de message ne respecte : chaque secteur de 512 ou 4 096 octets doit être chiffré à taille constante, sans place pour un IV ni pour une étiquette d’authentification, et réécrit indépendamment des autres. La réponse est le tweak : une valeur publique, ici le numéro de secteur, qui modifie la permutation sans changer la clé. Deux secteurs identiques à des emplacements différents donnent ainsi des chiffrés différents.

Le XTS (XEX-based Tweaked-codebook mode with ciphertext Stealing), normalisé par l’IEEE (Institute of Electrical and Electronics Engineers) 1619 en 2007 et repris par la NIST SP 800-38E, est le mode de référence. Il utilise deux clés AES : l’une chiffre le tweak, l’autre les données, d’où les clés de 256 bits pour « AES-128-XTS » et de 512 bits pour « AES-256-XTS ». Le vol de chiffré (ciphertext stealing) gère un dernier bloc incomplet sans remplissage. C’est ce mode qu’emploient LUKS (Linux Unified Key Setup) sous la forme aes-xts-plain64, BitLocker depuis Windows 10 version 1511 ou FileVault 2.

Fenêtre de terminal
cryptsetup luksDump /dev/sdb2 | grep -i cipher
# cipher: aes-xts-plain64

Ses limites découlent de sa conception. XTS reste déterministe à position fixe : un attaquant qui compare deux instantanés du même volume voit quels blocs de 16 octets ont changé, et lesquels sont revenus à une valeur antérieure. Il est malléable à la granularité du bloc : modifier 16 octets chiffrés produit 16 octets clairs aléatoires, sans alerte. Il ne détecte ni le remplacement d’un secteur par sa version ancienne ni la restauration d’un volume entier. Chiffrer un disque protège donc contre le vol de la machine éteinte ; cela ne garantit pas l’intégrité face à un attaquant ayant accès au stockage, et la promesse inverse, lue dans plus d’un dossier d’architecture, relève de l’optimisme commercial. La fiche protéger les données au repos traite les couches au-dessus du disque.

Pour le stockage de fichiers, la contrainte de taille disparaît : rien n’empêche d’ajouter un nonce et une étiquette à chaque fichier, ou à chaque segment de 64 Kio pour garder l’accès direct.

Un mode AEAD (Authenticated Encryption with Associated Data) chiffre et authentifie en une seule primitive, et authentifie en plus des données associées laissées en clair, comme un en-tête ou un numéro de version. Les deux références actuelles sont AES-GCM (Galois/Counter Mode, NIST SP 800-38D), qui combine CTR et un authentifiant polynomial, et ChaCha20-Poly1305 (RFC 8439, Request for Comments). La fiche chiffrer et authentifier en une seule opération les détaille.

CritèreCBC + HMAC (chiffrer puis authentifier)AEAD (GCM, ChaCha20-Poly1305)
Clésdeux clés distinctesune clé
Ordre des opérationsà implémenter soi-mêmefixé par la primitive
ParamètreIV imprévisiblenonce unique, 96 bits
Remplissageouinon
Étiquette16 à 32 octets selon troncature16 octets
Erreur typiqueMAC calculé sur le clair, ou vérifié après le paddingnonce réutilisé

Maintenir CBC-HMAC reste raisonnable quand un format ou un protocole l’impose et que la construction est correcte : HMAC sur l’IV et le chiffré, vérification en temps constant avant tout déchiffrement, clés séparées. JWE (JSON Web Encryption, JSON pour JavaScript Object Notation) définit par exemple A128CBC-HS256 dans la RFC 7518 selon ce principe. Refuser un mode s’impose dès qu’il n’est pas authentifié, qu’il s’agisse d’ECB, de CBC nu ou de CTR nu, et dès qu’un format permet de choisir le mode depuis les données reçues : un repli vers un mode faible offert à l’attaquant est un repli que l’attaquant prendra.

Une précaution concerne GCM : la réutilisation d’un nonce y est pire qu’en CTR, car elle révèle en plus de quoi calculer la clé d’authentification interne et donc de forger des messages. Avec des nonces aléatoires de 96 bits, la SP 800-38D limite l’usage à 2^32 messages par clé. Lorsque l’unicité ne peut pas être garantie, AES-GCM-SIV (RFC 8452) dégrade la fuite à la seule égalité de messages identiques.

Une migration se prépare dans le format. Un octet de version et un identifiant de clé en tête de chaque objet permettent de reconnaître l’ancien format, de le déchiffrer en lecture, et d’écrire systématiquement dans le nouveau. Les données anciennes sont rechiffrées au fil des accès ou par une tâche de fond, puis la lecture de l’ancien format est désactivée à une date fixée, faute de quoi elle survit dix ans par prudence. Les tests de non-régression couvrent les vecteurs de test connus de chaque mode, le rejet d’un chiffré dont un seul bit a été modifié, le rejet d’une étiquette tronquée, l’unicité des nonces sur un grand nombre de chiffrements, et l’absence de tout chemin de code qui accepte un objet sans étiquette.

PointRéponse attendue
PrimitiveAEAD : AES-256-GCM ou ChaCha20-Poly1305
Nonceunique par clé, stocké en clair en tête
Fichiers volumineuxdécoupage en segments authentifiés, numéro de segment et marqueur de fin dans les données associées
En-têteversion, identifiant de clé, nonce, tous authentifiés
Cléissue d’un générateur sûr ou d’une dérivation, jamais d’un mot de passe brut
Échec de vérificationaucun octet clair rendu, erreur unique
Repliaucun mode non authentifié accepté en lecture après migration

IV aléatoire et compteur unique ne sont pas interchangeables. CBC exige l’imprévisibilité : un compteur y est une faute. CTR et GCM exigent l’unicité : un tirage aléatoire y convient tant que le nombre de messages reste loin de la limite des anniversaires, un compteur persistant aussi, à condition de survivre aux redémarrages et aux clones de machines virtuelles.

CTR et chiffrement par flot se ressemblent dans l’usage, puisque CTR fabrique un flot de clé combiné par XOR. Mais un chiffrement par flot natif comme ChaCha20 est conçu pour cela, avec ses propres marges, tandis que CTR hérite des propriétés et de la taille de bloc de l’algorithme sous-jacent. Les deux partagent la même fragilité : flot réutilisé, secret perdu. La fiche chiffrement par flot et état prolonge la comparaison.

XTS et AEAD chiffrent tous deux des données stockées ; seul le second détecte une modification. Padding et bourrage de format n’ont rien en commun hormis le nom : le premier est une exigence du mode, vérifiée au déchiffrement, le second aligne des champs dans une structure de fichier et n’a aucune valeur cryptographique.

Les modes hérités et leurs histoires sont rassemblés dans chiffrer par blocs, l’héritage.

Sans mode authentifié, le déchiffrement accepte tout ce qu’on lui présente : un bloc rejoué, un bloc déplacé ou un octet modifié produit un résultat que l’application traite comme une donnée légitime. Un volume chiffré sans contrôle d’intégrité peut ainsi être altéré secteur par secteur, ou ramené à un instantané ancien, sans qu’aucune vérification ne proteste. Le repli toléré vers un mode historique rouvre en outre l’oracle de remplissage, mécanisme illustré par la brèche « la question qui rend le module bavard ». Enfin, un compteur réutilisé ne produit aucun message d’erreur : la fuite est silencieuse, se constate après coup, et expose chaque paire de messages chiffrés avec le même compteur et la même clé.

Un chiffrement par bloc est une opération qui transforme, avec une clé, un petit morceau de données de taille fixe, par exemple 16 octets, en un morceau illisible de même taille. Un vrai message est plus long : il faut donc le découper en blocs et décider comment les enchaîner. Cette règle d’enchaînement s’appelle un mode opératoire. Le mode le plus naïf chiffre chaque bloc séparément avec la même clé ; deux blocs identiques donnent alors deux blocs chiffrés identiques, ce qui laisse voir la structure du message, et ce mode est à éviter. D’autres modes mélangent chaque bloc avec le résultat du bloc précédent, en partant d’une valeur de départ appelée vecteur d’initialisation. Ils imposent souvent de compléter le dernier bloc par un remplissage, et si le programme signale différemment un remplissage incorrect, un attaquant peut s’en servir pour déchiffrer le message. Un troisième type de mode chiffre un compteur qui augmente à chaque bloc et mélange le résultat avec le message : réutiliser le même compteur avec la même clé révèle le contenu. Pour les disques, un mode spécial tient compte de la position de chaque secteur, mais ne protège pas vraiment contre les modifications. C’est la limite commune de tous ces modes : ils cachent le contenu sans détecter qu’on l’a modifié. Les usages modernes préfèrent donc des modes authentifiés, qui chiffrent et ajoutent une empreinte de contrôle vérifiée avant toute lecture.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.