Aller au contenu

Chiffrement par flot : état, compteurs et réutilisation interdite

Chiffrement par flot : état, compteurs et réutilisation interdite

Section intitulée « Chiffrement par flot : état, compteurs et réutilisation interdite »

Le chiffrement par flot est l’héritier pratique du masque à usage unique : au lieu d’une clé aussi longue que le message, il étire une clé courte en un flux pseudo-aléatoire, puis combine ce flux avec les données par XOR (ou exclusif). La famille est rapide, simple à implémenter, et elle chiffre octet par octet sans remplissage. Elle a aussi une règle unique et impitoyable : un même flux ne doit jamais servir deux fois. Ce chapitre présente le principe, les constructions historiques et modernes, la gestion de l’état et des compteurs, puis les usages actuels, de TLS (Transport Layer Security) aux réseaux privés virtuels.

→ Lire l’explication complète de la marmotte

Un chiffrement par flot prend trois entrées : une clé secrète, typiquement de 128 ou 256 bits, un nonce (number used once), valeur publique qui doit être unique pour chaque message chiffré sous cette clé, et une position dans le flux. Il produit un flux de clé KS, et le chiffrement se résume à C = M ⊕ KS. Le déchiffrement est identique : M = C ⊕ KS. Le chiffré a exactement la longueur du clair, ce qui est commode pour un protocole et indiscret pour la confidentialité, puisque la taille du message reste visible.

Le nonce n’a pas besoin d’être secret ni imprévisible : il doit être unique. Un compteur de messages convient parfaitement, un tirage aléatoire aussi à condition que l’espace soit assez grand pour rendre les collisions négligeables. Le couple (clé, nonce) désigne un flux ; deux messages chiffrés avec le même couple partagent le même flux, et C1 ⊕ C2 = M1 ⊕ M2, exactement comme pour un masque unique réutilisé, expliqué dans Du chiffre manuel au chiffrement moderne.

La synchronisation distingue deux sous-familles. Dans un chiffrement synchrone, le flux ne dépend que de la clé, du nonce et de la position ; émetteur et récepteur doivent donc savoir à quelle position ils se trouvent. Un octet perdu en transmission décale tout le reste, un octet corrompu n’altère que lui-même. Dans un chiffrement auto-synchronisant, le flux dépend des derniers octets chiffrés, ce qui permet de se recaler après une perte, au prix d’une propagation d’erreur limitée ; le mode CFB (Cipher Feedback) en est l’exemple classique. Les constructions modernes sont presque toutes synchrones et indexées par position, ce qui permet d’accéder directement au milieu d’un flux.

Dernière propriété, héritée du XOR : la malléabilité. Inverser un bit du chiffré inverse le bit correspondant du clair. Un chiffrement par flot nu n’offre donc aucune intégrité ; il doit être associé à un code d’authentification, ce que font les constructions AEAD (Authenticated Encryption with Associated Data) décrites dans Chiffrer et authentifier en une seule construction.

Le LFSR (Linear Feedback Shift Register) est un registre de n bits qui, à chaque coup d’horloge, se décale d’une position et réinjecte en entrée le XOR de certains bits, choisis par un polynôme de rétroaction. Avec un polynôme primitif, il parcourt les 2ⁿ − 1 états non nuls avant de revenir au départ, et sa sortie a d’excellentes propriétés statistiques. Il coûte quelques portes logiques, d’où son succès dans le matériel des années 1970 à 1990.

Sa linéarité le condamne pourtant. L’algorithme de Berlekamp-Massey reconstruit le polynôme et l’état d’un LFSR de longueur L à partir de 2L bits consécutifs de sortie. Un LFSR de 128 bits utilisé seul se casse donc avec 256 bits de flux connu, ce qu’un en-tête de fichier prévisible fournit sans effort. Une suite qui passe tous les tests statistiques peut être parfaitement prévisible : les tests mesurent l’apparence du hasard, pas la difficulté de prédire.

Pour casser la linéarité, plusieurs voies ont été explorées : combiner plusieurs LFSR par une fonction booléenne non linéaire (le générateur de Geffe, cassé par des attaques par corrélation), filtrer l’état d’un seul registre par une telle fonction, faire piloter l’horloge d’un registre par un autre (générateurs à horloge irrégulière, shrinking generator), mettre des générateurs en cascade, ou remplacer la rétroaction linéaire par une rétroaction non linéaire, d’où le NLFSR (Nonlinear Feedback Shift Register). Le FCSR (Feedback with Carry Shift Register) remplace le XOR par une addition avec retenue. Les générateurs additifs, de type Fibonacci retardé, additionnent des mots plutôt que des bits pour aller plus vite en logiciel. Produire plusieurs flux indépendants à partir d’un seul générateur, par entrelacement ou découpage, est une source classique d’erreurs, car les flux restent corrélés par l’état commun.

L’A5/1 du GSM (Global System for Mobile Communications) illustre ce courant : trois LFSR de 19, 22 et 23 bits, avancés selon une règle de majorité, et une clé de 64 bits. Il a été cassé par plusieurs attaques publiées à partir de la fin des années 1990, puis rendu pratique par des tables précalculées diffusées publiquement autour de 2009. Grain, sélectionné par le projet européen eSTREAM en 2008 dans la catégorie matérielle, et sa variante Grain-128a combinent un LFSR et un NLFSR ; c’est une conception moderne, analysée publiquement, destinée aux circuits très contraints.

Une autre école a cherché la sécurité dans la complexité du système plutôt que dans une structure analysable : tables dépendantes de la clé, opérations mixtes, conception empirique. RC4, conçu par Ron Rivest en 1987 et divulgué anonymement en 1994, en est le représentant le plus célèbre : un tableau de 256 octets permuté par la clé, une boucle de quelques lignes. SEAL, d’IBM, et WAKE appartiennent à la même génération logicielle. Le chiffrement intégré au format PKZIP historique, distinct de l’AES des archives récentes, tombe face à une attaque à clair connu publiée en 1994 qui n’exige qu’une douzaine d’octets de clair. Les générateurs Hughes XPD/KPD, Nanoteq, Rambutan, Gifford ou l’Algorithm M de Knuth, répertoriés dans la littérature des années 1990, relèvent de l’histoire : cassés, propriétaires ou jamais soumis à une analyse publique suffisante. Les citer dans un inventaire a un seul sens : les retrouver pour les retirer.

Algorithmes cassés ou hérités, algorithmes logiciels modernes

Section intitulée « Algorithmes cassés ou hérités, algorithmes logiciels modernes »

RC4 montre comment une primitive meurt lentement. Les premiers octets de son flux sont biaisés, ce qui a permis dès 2001 de casser le WEP (Wired Equivalent Privacy) du Wi-Fi, qui concaténait naïvement un vecteur d’initialisation à la clé. D’autres biais, exploitables sur des millions de connexions répétant le même secret, ont conduit le RFC 7465, en 2015, à interdire RC4 dans TLS.

La relève logicielle vient de Daniel J. Bernstein : Salsa20 (2005), retenu par eSTREAM dans la catégorie logicielle, puis ChaCha20 (2008), qui améliore la diffusion par tour. ChaCha20 applique vingt tours d’additions, rotations et XOR sur un état de seize mots de 32 bits contenant des constantes, la clé de 256 bits, un compteur et le nonce. Sans table indexée par des données secrètes, il s’implémente naturellement en temps constant, ce qui le protège des attaques par cache évoquées dans Quand l’implémentation parle.

Dans ChaCha20 tel que normalisé par le RFC 8439, le nonce fait 96 bits et le compteur de blocs 32 bits ; chaque bloc produit 64 octets de flux. Un même nonce couvre donc au plus 2³² × 64 octets, soit 256 Gio. Le mode CTR (Counter) d’un chiffrement par blocs fonctionne de la même façon : il chiffre successivement nonce || compteur, et le résultat sert de flux. Dans les deux cas, accéder à l’octet situé à l’offset p revient à calculer le bloc p / 64 (ou p / 16 pour l’AES, Advanced Encryption Standard) sans parcourir le début, ce qui autorise le chiffrement de disques ou de fichiers en accès aléatoire. Les générateurs à état interne, comme RC4 ou un LFSR, n’offrent pas cette facilité : pour aller à la position p, il faut tout dérouler.

Le danger réel se loge dans l’exploitation. Un service qui tient son compteur de messages en mémoire et redémarre après un plantage repart de zéro, et rechiffre les messages suivants avec des nonces déjà consommés. Deux processus qui partagent une clé et tirent chacun leurs nonces d’un compteur local produiront fatalement les mêmes valeurs. Une machine virtuelle restaurée depuis un instantané rejoue l’état de son générateur. Les parades sont connues : persister le compteur en avançant par tranches réservées avant usage, attribuer à chaque émetteur une clé distincte ou un préfixe de nonce distinct, ou tirer des nonces aléatoires dans un espace assez large. Pour un nonce aléatoire de 96 bits, le NIST (National Institute of Standards and Technology) limite l’usage d’AES-GCM (Galois/Counter Mode) à 2³² messages par clé ; XChaCha20, avec un nonce de 192 bits, rend le tirage aléatoire confortable.

La règle à retenir tient en une ligne : jamais deux fois le même couple (clé, nonce), quelle que soit la cause, redémarrage, réplication, clonage ou restauration.

ChaCha20-Poly1305 associe ChaCha20 au code d’authentification Poly1305 pour former un AEAD normalisé par le RFC 8439. Il figure parmi les trois suites de chiffrement recommandées par TLS 1.3, chiffre les tunnels WireGuard et existe dans OpenSSH sous le nom chacha20-poly1305@openssh.com. Les messageries chiffrées de bout en bout l’emploient aussi couramment, au côté d’AES. Son atout principal apparaît sur le matériel dépourvu d’instructions AES : un processeur mobile ou embarqué sans extension cryptographique exécute ChaCha20 nettement plus vite qu’une implémentation logicielle d’AES, et sans les risques de fuite temporelle liés aux tables. Sur un processeur x86 équipé d’AES-NI, AES-GCM reprend en général l’avantage. Le choix entre les deux est traité dans Choisir, combiner et migrer.

RC4, lui, est déprécié partout où un référentiel s’exprime. Il survit pourtant dans des recoins : anciens équipements, archives, et type de chiffrement RC4-HMAC de Kerberos encore accepté dans certains domaines Active Directory pour compatibilité.

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
import os
cle, nonce = os.urandom(32), os.urandom(16) # nonce 16 octets : compteur + nonce
def chacha(m):
return Cipher(algorithms.ChaCha20(cle, nonce), mode=None).encryptor().update(m)
c1 = chacha(b"solde: 1200 EUR, compte A")
c2 = chacha(b"solde: 9800 EUR, compte B") # même clé, même nonce : erreur
x = bytes(a ^ b for a, b in zip(c1, c2))
print(x) # zéros partout où les clairs coïncident

Le résultat contient des octets nuls aux positions où les deux clairs sont identiques (solde: , EUR, compte) et le XOR des chiffres ailleurs. Connaître un seul des deux messages livre l’autre en entier. ChaCha20 n’est pas en cause : c’est l’emploi qui est fautif, et aucun algorithme ne survit à ce traitement.

Fenêtre de terminal
# Débit comparé des deux AEAD sur la machine courante
openssl speed -evp aes-256-gcm
openssl speed -evp chacha20-poly1305
# Sur x86 : vérifier la présence des instructions AES
grep -o -m1 -w aes /proc/cpuinfo

Sur un serveur x86 récent, AES-GCM dépasse en général ChaCha20-Poly1305. Sur une carte à processeur ARM ancien sans extensions cryptographiques, l’ordre s’inverse nettement. La mesure locale vaut mieux que n’importe quel tableau publié, et elle prend deux minutes.

Fenêtre de terminal
# Suites TLS proposées par un serveur, à la recherche de RC4
nmap --script ssl-enum-ciphers -p 443 serveur.example
# Configurations et code qui le mentionnent encore
grep -rniE 'rc4|arcfour' /etc/ ./src/ 2>/dev/null

Chaque occurrence se classe : supprimable immédiatement, dépendante d’un client identifié, ou nécessaire à la lecture d’archives. Seule la dernière catégorie justifie de garder RC4, et uniquement dans un outil isolé de déchiffrement, jamais dans un service exposé. L’inventaire général est décrit dans Gouverner la cryptographie.

Réinitialiser un compteur au redémarrage reste la faute la plus fréquente, et la plus silencieuse : tout fonctionne, tout se déchiffre, et la confidentialité a disparu. Réutiliser un nonce par une valeur codée en dur, souvent héritée d’un exemple de documentation, produit le même effet sur tous les messages. Croire qu’un flux à l’apparence aléatoire est sûr conduit droit au LFSR nu, qui réussit les tests et tombe en quelques centaines de bits. Maintenir RC4 « pour compatibilité » sans cloisonner l’usage revient à laisser à l’attaquant le choix de l’algorithme, puisqu’une négociation se rabat volontiers sur le plus faible commun.

Un CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) et un chiffrement par flot partagent souvent le même moteur, ChaCha20 sert d’ailleurs de générateur dans le noyau Linux, mais leurs contrats diffèrent : le premier produit de l’aléa imprévisible, se ressème et ne doit jamais être rejoué ; le second doit au contraire être rejouable à l’identique par le destinataire. Le mode CTR transforme un chiffrement par blocs en flot, alors qu’un chiffrement par flot natif comme ChaCha20 est conçu directement pour produire un flux ; les deux partagent les mêmes règles de nonce. L’état interne d’un générateur évolue à chaque octet et n’est pas la clé, même s’il en dérive : le compromettre suffit pourtant à prédire la suite du flux. Enfin, un nonce unique et un nonce imprévisible sont deux exigences distinctes : les chiffrements par flot exigent l’unicité, le mode CBC (Cipher Block Chaining) exige en plus l’imprévisibilité de son vecteur d’initialisation, comme l’explique Transformer un bloc en chiffrement de message.

Sans discipline sur les nonces et les compteurs, le chiffrement par flot échoue sans bruit : aucun message d’erreur, aucun déchiffrement raté, seulement deux textes chiffrés qui, combinés, livrent le mélange des deux clairs à qui les a interceptés. Le défaut se déclenche typiquement au redémarrage, quand un compteur non sauvegardé repart de zéro, ou lors de la parallélisation, quand deux processus tirent la même position ; la faute survient donc au moment où personne ne regarde. Et comme un flux sans authentification se modifie bit à bit, un attaquant peut inverser un bit précis du message sans connaître la clé, exactement à la position qui l’intéresse.

Un chiffrement par flot fabrique, à partir d’une clé secrète et d’un nombre utilisé une seule fois, appelé nonce, une longue suite de bits qui semble aléatoire. Le message est combiné bit à bit avec cette suite par une opération appelée « ou exclusif » : chaque bit du message est inversé là où la suite contient un 1. Le destinataire, qui possède la même clé et le même nonce, régénère la même suite et refait la même opération, ce qui rend le message d’origine. La sécurité repose entièrement sur une règle : une même suite ne doit jamais servir deux fois. Si deux messages sont chiffrés avec la même clé et le même nonce, combiner les deux textes chiffrés fait disparaître la suite et laisse le mélange des deux messages, que l’on peut souvent démêler. Le générateur garde un état interne, ou un compteur qui indique la position dans la suite. Après un plantage, un compteur remis à zéro recommence la même suite : c’est l’erreur classique. Les anciens générateurs, construits avec des registres à décalage, ou l’algorithme appelé RC4, sont cassés ou abandonnés. Les algorithmes modernes comme ChaCha20, associés à une vérification d’intégrité, servent dans les connexions sécurisées et sur le matériel dépourvu d’accélération pour d’autres chiffrements.

À lire aussi

Pages voisines

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