Aller au contenu

Produire de l’aléa utilisable en cryptographie

Presque tout secret cryptographique commence par un tirage au hasard : clés, nonces, sels, défis d’authentification, jetons de session. Un algorithme impeccable alimenté par un aléa prévisible ne protège plus rien, et la faute ne laisse aucune trace visible, puisque les valeurs produites ont l’air parfaitement aléatoires. Ce chapitre explique ce qu’est un aléa utilisable en cryptographie, d’où le système le tire, comment le consommer correctement et pourquoi les tests statistiques ne suffisent pas à le valider.

→ Lire l’explication complète de la marmotte

Aléatoire ne veut pas dire visuellement désordonné

Section intitulée « Aléatoire ne veut pas dire visuellement désordonné »

Une suite aléatoire se caractérise par quatre propriétés distinctes. La distribution de probabilités décrit la fréquence attendue de chaque valeur : pour un octet uniforme, chacune des 256 valeurs a une probabilité de 1/256. Le biais mesure l’écart à cette distribution : un générateur qui produit des bits à 1 dans 51 % des cas est biaisé, même si cela ne se voit pas à l’œil. L’indépendance exige qu’aucune valeur ne renseigne sur les suivantes ou les précédentes. Enfin, l’imprévisibilité est la seule propriété qui compte vraiment pour la sécurité : un attaquant qui connaît l’algorithme, l’environnement et une partie des sorties ne doit pas pouvoir deviner la suite mieux qu’au hasard.

Les décimales de π passent pour très bien distribuées et ne sont évidemment pas imprévisibles. Une suite qui « a l’air » désordonnée ne prouve donc rien. L’inverse est vrai aussi : un humain qui tape des touches « au hasard » produit une suite très structurée, ce qui disqualifie définitivement le clavier comme source de clés, pour ceux qui en doutaient encore.

L’entropie mesure l’incertitude d’une source, en bits : une valeur à 128 bits d’entropie exige en moyenne 2^127 essais pour être devinée. Le noyau la récolte à partir d’événements système dont le moment exact est difficile à prédire : interruptions, temps d’accès aux disques, arrivée des paquets réseau, gigue de l’horloge du processeur. Il dispose souvent aussi de sources matérielles : instructions RDRAND et RDSEED des processeurs Intel (technologie Secure Key, depuis 2012) et AMD, puce TPM (Trusted Platform Module), générateurs physiques dédiés.

Le démarrage est le moment critique. Une machine virtuelle clonée, un routeur sans disque ni clavier ou un conteneur lancé en série disposent de très peu d’événements imprévisibles au cours des premières secondes. Une étude de 2012 (« Mining your Ps and Qs », Heninger et coll.) a montré que de nombreux équipements embarqués généraient leurs clés RSA juste après le premier démarrage, au point que des milliers de clés partageaient un facteur premier, ce qui permettait de les factoriser par un simple calcul de PGCD.

Le noyau procède donc à une estimation prudente : il crédite chaque événement d’une quantité d’entropie volontairement sous-évaluée. Il mélange les entrées dans un pool, une réserve de quelques centaines d’octets passée dans une fonction de hachage, puis alimente un générateur dont il renouvelle régulièrement la graine, c’est le reseeding. Ce renouvellement limite les dégâts d’une compromission passagère de l’état interne.

Fortuna, conçu par Ferguson et Schneier en 2003, pousse cette logique plus loin : 32 pools reçoivent les événements à tour de rôle, et le pool numéro i ne participe à un réensemencement qu’une fois sur 2^i. Un attaquant qui injecte des événements contrôlés ou observe une partie des sources finit toujours par perdre la course, car les pools rarement utilisés accumulent assez d’entropie pour rétablir la situation, sans qu’il soit besoin d’estimer précisément l’entropie entrante. FreeBSD l’utilise pour son générateur noyau.

On distingue trois catégories. Un RNG (Random Number Generator) physique mesure un phénomène réel. Un PRNG (Pseudo-Random Number Generator) étire une graine en une longue suite déterministe ; il est conçu pour la vitesse et la qualité statistique, pour les simulations ou les jeux. Un CSPRNG (Cryptographically Secure PRNG) garantit en plus que ses sorties sont imprévisibles sans connaissance de l’état interne, et que la découverte de l’état actuel ne révèle pas les sorties passées. Le générateur du noyau Linux produit sa sortie avec ChaCha20 ; les générateurs déterministes normalisés par le NIST dans la publication SP 800-90A s’appuient sur AES ou HMAC.

Les API modernes suffisent dans tous les cas courants :

EnvironnementInterface recommandée
Linuxappel système getrandom(), ou /dev/urandom
WindowsBCryptGenRandom
Pythonmodule secrets, ou os.urandom
Javajava.security.SecureRandom
Gopaquet crypto/rand
Node.jscrypto.randomBytes, ou crypto.getRandomValues
Ligne de commandeopenssl rand -hex 32

La question du blocage a longtemps semé la confusion. Historiquement, /dev/random bloquait dès que l’estimation d’entropie baissait, et /dev/urandom ne bloquait jamais. Depuis Linux 5.6, /dev/random ne bloque plus qu’avant l’initialisation du générateur, et getrandom() sans option attend précisément cette initialisation, puis ne bloque plus jamais. C’est le bon comportement : un générateur correctement ensemencé ne s’« use » pas. La seule fenêtre dangereuse est celle du démarrage, et /dev/urandom lu trop tôt peut y renvoyer des valeurs faibles sans prévenir.

Les usages diffèrent par leurs exigences. Une clé symétrique demande 128 ou 256 bits issus du CSPRNG. Un jeton de session ou de réinitialisation de mot de passe demande au moins 128 bits imprévisibles, encodés sans perte d’entropie. Un nonce, valeur à usage unique, demande surtout l’unicité : pour AES-GCM, des nonces aléatoires de 96 bits imposent de limiter une même clé à 2^32 messages, sous peine de risque de collision non négligeable. Un compteur garantit l’unicité sans aléa, à condition de ne jamais revenir en arrière. Certaines signatures ont fait le choix déterministe : RFC 6979 pour ECDSA et Ed25519 dérivent le nonce de la clé et du message, ce qui supprime la dépendance à l’aléa au moment de signer.

Les batteries de tests, comme NIST SP 800-22, Dieharder ou TestU01, vérifient fréquences, séries, corrélations et motifs. Elles détectent un générateur cassé, un biais grossier, une sortie bloquée sur une valeur constante. Elles ne prouvent pas l’imprévisibilité. AES en mode compteur avec une clé publiée, ou même nulle, produit une suite qui passe tous ces tests et que n’importe qui peut prédire.

Les générateurs statistiquement propres mais prévisibles sont légion. Mersenne Twister, utilisé par le module random de Python, réussit la plupart des tests usuels, mais 624 sorties consécutives de 32 bits suffisent à reconstituer son état interne et à prédire toute la suite. java.util.Random est un générateur congruentiel linéaire à 48 bits d’état, retrouvable à partir de deux sorties. Le générateur Dual_EC_DRBG, normalisé puis retiré par le NIST à la suite des révélations de 2013, a montré qu’un générateur pouvait même contenir une porte dérobée mathématique, invisible à tout test statistique.

Les bugs de tirage modulo viennent ensuite d’un générateur parfaitement sain. Pour obtenir un chiffre de 0 à 9 à partir d’un octet uniforme, la tentation est d’écrire octet % 10. Or 256 = 25 × 10 + 6 : les valeurs 0 à 5 apparaissent 26 fois sur 256, les valeurs 6 à 9 seulement 25 fois. Le biais est faible, environ 4 % d’écart relatif, mais il s’accumule sur un code PIN ou une clé et se cumule mal avec certaines signatures, où quelques bits biaisés du nonce suffisent à retrouver la clé privée sur un grand nombre de signatures. La correction est le tirage par rejet : rejeter les octets supérieurs ou égaux à 250 et tirer à nouveau, ou utiliser la fonction prévue, comme secrets.randbelow(10).

Générer une clé et un nonce. En Python :

import secrets
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
cle = AESGCM.generate_key(bit_length=256) # tirée du CSPRNG du système
nonce = secrets.token_bytes(12) # 96 bits, à ne jamais réutiliser avec cette clé
jeton = secrets.token_urlsafe(32) # 256 bits encodés en Base64 pour une URL

Aucune graine n’apparaît dans ce code, et c’est voulu : la graine est l’affaire du noyau.

Montrer un biais modulo. On tire un million d’octets avec secrets.token_bytes, on calcule b % 10 et on compte : les valeurs 0 à 5 apparaissent environ 101 600 fois chacune, les valeurs 6 à 9 environ 97 700 fois. L’écart n’a rien d’un accident de mesure, il est arithmétique.

Comparer un PRNG généraliste et un CSPRNG. random.seed(1700000000) suivi de random.getrandbits(128) donne toujours la même valeur : qui devine l’horodatage du lancement devine la clé, et un horodatage à la seconde près sur une journée ne représente que 86 400 possibilités, soit moins de 17 bits d’entropie pour une clé qui en affiche 128. Le même tirage par secrets.token_bytes(16) ne se reproduit pas.

Documenter les dépendances d’entropie d’un service. Pour chaque service, on note l’interface utilisée, le moment du premier tirage (au démarrage, à la première requête), la nature de l’hôte (machine physique, machine virtuelle, conteneur), la présence d’une source matérielle exposée à l’invité (virtio-rng par exemple) et le comportement en cas de restauration d’instantané. Ce dernier point est critique : une machine virtuelle restaurée deux fois depuis le même instantané repart avec le même état de générateur en espace utilisateur et peut produire deux fois les mêmes nonces.

Initialiser une graine avec l’heure revient à publier sa clé dans le journal système. Utiliser un PRNG de simulation pour un jeton de session, parce que la fonction s’appelait random, est une erreur assez répandue pour avoir sa propre entrée dans la classification CWE (Common Weakness Enumeration), sous le numéro 338. Réutiliser un nonce après un plantage ou une restauration arrive quand l’application garde son propre générateur ou son compteur en mémoire et ne persiste pas l’état ; avec AES-GCM, deux messages chiffrés sous le même nonce révèlent le OU exclusif des clairs et permettent de forger des étiquettes. Considérer un RNG matériel comme preuve suffisante oublie qu’il peut être défaillant : certains processeurs AMD ont renvoyé la valeur constante 0xFFFFFFFF via RDRAND après une mise en veille, et Linux, par prudence, mélange la sortie de RDRAND dans son pool au lieu de lui faire une confiance exclusive. Enfin, confondre test statistique et sécurité cryptographique conduit à valider en recette un générateur qu’un attaquant prédit en production. La gestion de l’aléa dans le matériel dédié est traitée dans la page sur la génération aléatoire dans un HSM.

Hasard physique et CSPRNG : le premier fournit l’entropie initiale, souvent biaisée et lente ; le second l’étire en un flux rapide et uniforme. Les deux sont nécessaires. Entropie et longueur de sortie : une clé de 256 bits dérivée d’une graine de 32 bits n’a que 32 bits d’entropie, quelle que soit sa longueur affichée. L’incident Debian de 2008 (CVE-2008-0166) l’a illustré : un correctif malheureux limitait l’entropie d’OpenSSL à l’identifiant de processus, si bien que toutes les clés générées appartenaient à un ensemble de quelques dizaines de milliers par type et taille. Nonce aléatoire et nonce compteur : le premier se passe d’état mais tolère un nombre limité de messages par clé, le second exige un état fiable et persistant. Uniformité statistique et imprévisibilité : la première se mesure, la seconde se démontre par la conception du générateur et l’analyse de ses hypothèses. Les nonces et leurs usages sont repris dans la page sur le vocabulaire des clés.

Sans source d’aléa cryptographique, les secrets produits par un service ne sont plus des secrets mais des résultats de calcul : qui retrouve la graine retrouve d’un coup toutes les clés et tous les jetons émis depuis, sans avoir à en attaquer un seul. Des machines démarrées à l’identique et sans entropie propre peuvent produire les mêmes clés, ce qu’aucun test statistique ne signale puisque chaque sortie, prise isolément, paraît parfaite. Les signatures qui exigent un aléa neuf deviennent elles-mêmes une fuite : deux signatures tirées avec la même valeur suffisent à compromettre la clé. L’incident, enfin, se voit rarement : rien ne plante, tout fonctionne, y compris pour l’attaquant.

Beaucoup de secrets informatiques sont des nombres tirés au hasard : les clés de chiffrement, les valeurs à usage unique, les jetons de connexion. Leur sécurité repose sur une seule condition : un attaquant ne doit pas pouvoir prédire le nombre tiré. Une suite qui paraît désordonnée ne suffit pas. Elle peut passer les tests statistiques, qui vérifient par exemple que chaque valeur apparaît aussi souvent que les autres, tout en restant prévisible pour qui connaît son point de départ. Le système d’exploitation collecte donc de l’imprévisibilité, appelée entropie, à partir d’événements du système ou d’un composant matériel dédié. Il la mélange dans une réserve, puis s’en sert pour alimenter un générateur conçu pour la cryptographie, qui produit ensuite autant de nombres que nécessaire et se réalimente régulièrement. Les programmes doivent demander leurs nombres à ce générateur, par les fonctions prévues par le système. Les erreurs courantes sont connues : démarrer un générateur avec l’heure, qui se devine ; utiliser un générateur conçu pour les simulations ; tirer un nombre trop tôt au démarrage d’une machine, quand la réserve est encore presque vide ; ou ramener un grand nombre dans un intervalle par un calcul de reste qui favorise certaines valeurs. Un composant matériel de hasard ne suffit pas non plus à lui seul, car il faut faire confiance à ce composant.

À lire aussi

Pages voisines

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