Quatre familles de générateurs, huit tests statistiques, une attaque. Ce rapport cherche à savoir si « passer les tests statistiques » veut dire « être sûr ». La réponse est non, et la démonstration tient en deux lignes du tableau ci-dessous.
La thèse. Un test statistique constate l'absence de structure visible. Il ne dit rien de la prédictibilité. Ce sont deux propriétés distinctes, et seule la seconde protège un secret.
Deux résultats de ce banc d'essai suffisent à trancher : un générateur qui passe les huit tests et dont on prédit 100 % des sorties futures, et une source d'entropie rigoureusement nulle qui passe elle aussi les huit tests.
Échantillon de 1 000 000 bits par source, seuil de rejet à 1 %. Survolez une case pour lire la p-valeur.
| Monobit | Runs | Série max de 1 | Khi-deux | Shannon | Autocorr. 1-32 | Spectral | Incompress. | score | |
|---|---|---|---|---|---|---|---|---|---|
| LCG (octet de poids faible)PRNG | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | 4/8 |
| Mersenne Twister (MT19937)PRNG | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 8/8 |
| secrets (CSPRNG système)CSPRNG | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 8/8 |
| Jitter CPU — extraction naïveTRNG logiciel | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | 0/8 |
| Jitter CPU — brutTRNG logiciel | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | 0/8 |
| Jitter CPU — débiaisé (von Neumann)TRNG logiciel | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 2/8 |
| Jitter CPU — conditionné SHA-256TRNG logiciel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 8/8 |
| Source NULLE + SHA-256Contre-exemple | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 8/8 |
Lecture : « 8/8 » ne signifie pas « sûr ». Trois lignes affichent 8/8 pour trois raisons complètement différentes — dont une source qui ne contient aucune entropie.
Le générateur congruentiel linéaire est le mauvais élève de service. On publie son octet de poids faible, l'erreur classique : les bits de poids faible d'un LCG ont des périodes minuscules.
Il est pourtant loin d'échouer partout. Son entropie de Shannon vaut 8.00000 bits par octet — le maximum théorique exact — et son khi-deux sur les 256 valeurs d'octet donne p = 1, une uniformité parfaite. Sa distribution est irréprochable.
Et pourtant zlib le comprime à 0.0065 de sa
taille, soit un facteur 153. Une suite réellement
aléatoire est incompressible ; celle-ci est presque entièrement structurée.
Histogramme des valeurs d'octet. Le LCG (rouge) est une ligne parfaitement plate — plus plate que le CSPRNG, dont les fluctuations sont justement le signe qu'il est aléatoire. Une distribution trop parfaite est un symptôme, pas une qualité.
La leçon : l'entropie de Shannon et le khi-deux mesurent la distribution marginale, c'est-à-dire la fréquence de chaque valeur prise isolément. Ils sont aveugles à l'ordre des valeurs. Il faut des tests structurels — compression, spectral, autocorrélation — pour voir la séquence.
Autocorrélation aux décalages 1 à 32, en écarts-types. Au-delà de la ligne pointillée, la dépendance est statistiquement établie.
C'est ici que l'idée du générateur matériel maison rencontre la réalité. Notre source physique est le jitter d'horloge : on exécute une charge de calcul fixe et on mesure sa durée, qui varie sous l'effet du cache, de l'ordonnanceur, de la fréquence et des interruptions. Du vrai bruit physique, sans composant à souder.
L'extraction évidente consiste à prendre le bit de poids faible de l'écart entre deux
lectures d'horloge. Sous Windows, perf_counter_ns a une granularité de
100 ns : tous les écarts sont des multiples de 100, et leur bit de poids faible vaut
invariablement 0.
Mesure : P(bit = 1) = 0.0000, min-entropie 0,000 bit par bit. La source produit un flux de zéros à 8 Mbit/s. Elle « fonctionne », elle ne lève aucune erreur, et elle ne contient rien.
Avec une extraction correcte, la source vit : P(bit = 1) = 0.3970. Biaisée, donc, mais réelle. Le correctif classique est le débiaisage de von Neumann, qui garantit une sortie parfaitement équilibrée — à condition que les bits soient indépendants.
Après von Neumann, on mesure P(bit = 1) = 0.5024. Sur un million de bits, cet écart à 0,5 est massivement significatif. Ce n'est pas du bruit de mesure : c'est la preuve que les bits bruts ne sont pas indépendants. L'hypothèse de von Neumann est violée, donc sa garantie ne s'applique pas. Le flux débiaisé échoue encore 6 tests sur 8.
Min-entropie par bit — -log2(max(p0, p1)). C'est la
mesure qui compte pour une source d'entropie (NIST SP 800-90B) car elle se règle sur
l'issue la plus probable, donc sur le meilleur pari d'un attaquant. L'entropie de
Shannon, qui moyenne, est bien plus flatteuse.
Rendement de von Neumann : 21.9 % des bits survivent, pour un débit final de 76.2 kbit/s. Le CSPRNG du système délivre 15.4 Gbit/s — un rapport de 604 522 × en faveur du logiciel.
Débit de production, échelle logarithmique.
Dernière étape du montage classique : conditionner la sortie par une fonction de hachage. Appliqué à notre jitter débiaisé, SHA-256 fait passer la source de 2/8 à 8/8. Victoire ?
Alors appliquons exactement le même conditionnement à la source nulle — le flux de zéros de l'acte II, entropie mesurée à 0,000 bit. Résultat : 8/8 également, avec des p-valeurs tout aussi confortables.
Un générateur sans aucune entropie est statistiquement indiscernable d'un bon générateur. Les tests ne mesurent pas la source : ils mesurent le conditionneur. SHA-256 produit une sortie d'apparence parfaite à partir de n'importe quelle entrée, y compris un compteur.
C'est exactement pour cela qu'un générateur matériel maison est risqué. Si la diode vieillit, se sature ou se débranche, la sortie reste impeccable aux tests — et devient entièrement prédictible pour qui devine le compteur. Les vrais TRNG certifiés (AIS-31, NIST SP 800-90B) imposent pour cette raison des tests de santé sur la source brute, en continu, avant tout conditionnement.
MT19937 est le générateur de random en Python, de PHP, de Ruby, d'Excel.
Il passe les huit tests sans la moindre faiblesse. Sa période est de 219937−1.
Il conserve 624 mots de 32 bits d'état interne. Chaque sortie est produite en appliquant à un mot d'état une fonction de « tempering » faite de décalages et de XOR. Cette fonction est bijective : elle brouille, elle ne cache pas. On l'inverse.
y ^= y >> 18 → inversible en une passe y ^= (y << 15) & 0xEFC60000 → inversible par itération y ^= (y << 7) & 0x9D2C5680 → inversible par itération y ^= y >> 11 → inversible par itération
Observer 624 sorties consécutives, c'est donc lire l'état interne complet. On reconstruit un clone, et on prédit la suite.
Aucun des huit tests ne voit cette faiblesse, et aucun ne le pourrait : la sortie de MT19937 est statistiquement excellente. La faille n'est pas dans la distribution, elle est dans la structure de l'algorithme.
| Constat | Conséquence |
|---|---|
| Le LCG a une entropie de Shannon parfaite et se comprime d'un facteur 153 | Mesurer l'entropie d'une sortie ne dit rien de sa qualité |
| Le jitter naïf produit 0 bit d'entropie sans lever d'erreur | Une source physique doit être mesurée, jamais supposée |
| Von Neumann laisse un déséquilibre de 0.5024 | Corriger le biais ne corrige pas la corrélation |
| Une source nulle conditionnée par SHA-256 passe les 8 tests | Les tests jugent le conditionneur, pas la source |
| MT19937 passe les 8 tests et se prédit à 100 % | Réussir les tests n'est pas une preuve de sécurité |
| Le CSPRNG est 604 522 × plus rapide que le TRNG logiciel | Fabriquer son propre aléa coûte cher et rapporte moins |
D'où la conclusion pratique, qui est aussi le principe de conception de KeyMint : pour un secret, on utilise le CSPRNG du système. Il est déjà alimenté par l'entropie matérielle, déjà conditionné, déjà audité — et il ne meurt pas en silence.
Les huit tests sont implémentés dans ce projet, sans bibliothèque de statistiques. Les p-valeurs reposent sur une fonction gamma incomplète régularisée écrite à la main, validée contre SciPy dans la suite de tests. Chaque test est par ailleurs éprouvé sur des suites délibérément défectueuses dont on connaît le défaut — un test qui ne rejette jamais rien ne vaut rien.
py run_lab.py # banc d'essai → report/results.json py make_report.py # → report/index.html + index.en.html py tests/test_rnglab.py