Génération de nombres aléatoires sécurisés : base de la crypto forte
La génération aléatoire sécurisée est critique pour le chiffrement. Apprenez comment crypto.getRandomValues fonctionne.
La génération de nombres aléatoires sécurisés est le fondement sur lequel repose chaque autre pièce de la cryptographie. En JavaScript, crypto.getRandomValues(buffer) remplit un TypedArray avec des octets aléatoires cryptographiquement sûrs provenant du CSPRNG du système d'exploitation (/dev/urandom sur Linux/macOS, BCryptGenRandom sur Windows, SecRandomCopyBytes sur iOS/macOS). N'utilisez jamais Math.random() pour quoi que ce soit touchant à la sécurité — c'est un PRNG de style Mulberry32 ou xorshift conçu pour la vitesse, pas pour l'imprévisibilité, et sa sortie est prévisible après l'observation de quelques échantillons. Un RNG faible compromet les clés AES, les handshakes TLS, l'unicité des nonces en GCM, l'impossibilité de deviner les jetons, et toutes les autres primitives de sécurité qui dépendent de bits imprévisibles.
La différence entre aléatoire et aléatoire cryptographique
Un PRNG (générateur de nombres pseudo-aléatoires) produit un flux déterministe à partir d'une graine. Avec la graine et l'algorithme, vous pouvez reproduire chaque sortie. Convient aux jeux, simulations et méthodes de Monte Carlo. Catastrophique pour la cryptographie.
Un CSPRNG (PRNG cryptographiquement sûr) est alimenté par une véritable source d'entropie (bruit thermique, timing des interruptions, instructions RNG matérielles comme RDSEED d'Intel) et sa conception garantit que la sortie est computationnellement indiscernable d'un vrai aléatoire, et que l'observation des sorties passées n'aide pas à prédire les sorties futures.
JavaScript vous offre les deux. Math.random() est un PRNG. crypto.getRandomValues() est un wrapper sur le CSPRNG du système d'exploitation. Une ligne de code de différence, une immense différence de sécurité.
L'utilisation correcte canonique
// Générer 256 bits d'octets aléatoires pour une clé AES
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
// Générer un nonce GCM de 96 bits
const nonce = crypto.getRandomValues(new Uint8Array(12));
// Générer un sel de 128 bits
const salt = crypto.getRandomValues(new Uint8Array(16));
// Générer un jeton sûr pour les URL
const tokenBytes = crypto.getRandomValues(new Uint8Array(32));
const token = btoa(String.fromCharCode(...tokenBytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
crypto.getRandomValues() est synchrone, remplit le buffer sur place, retourne le buffer. La taille maximale de requête est de 65 536 octets en un seul appel (un quota imposé par la spec pour éviter le blocage). Pour du matériel aléatoire plus important, appelez de façon répétée.
Équivalents Node.js
const { randomBytes, randomFillSync, webcrypto } = require('crypto');
const keyBytes = randomBytes(32); // Retourne un Buffer
// Ou compatible Web Crypto
const nonce = webcrypto.getRandomValues(new Uint8Array(12));
randomBytes de Node tire du même CSPRNG sous-jacent que Web Crypto. Utilisez le style d'API qui convient à votre code. Pour du code isomorphe fonctionnant dans les deux environnements, webcrypto.getRandomValues correspond exactement au navigateur.
Pourquoi Math.random échoue
V8 (Chrome/Node), SpiderMonkey (Firefox) et JavaScriptCore (Safari) implémentent tous Math.random() comme un PRNG rapide sans garanties cryptographiques. V8 utilise une variante xorshift128+. Des chercheurs ont démontré qu'après avoir observé ~5 sorties, un attaquant peut récupérer l'état interne et prédire toutes les sorties futures. En 2015, Mike Pound et ses collègues ont inversé l'état de Math.random de V8 dans des programmes de bug bounty réels.
Si vous utilisez Math.random() pour générer des jetons de session, des liens de réinitialisation de mot de passe, des nonces de chiffrement ou des identifiants de partage, les attaquants qui en observent quelques-uns peuvent prédire le reste. Ce n'est pas théorique — c'est une classe de bugs courante dans les audits.
Mauvaises utilisations courantes
Alimenter un PRNG de bibliothèque avec Math.random() :
// MAUVAIS
const seed = Math.floor(Math.random() * 2**32);
Rien en aval ne peut être plus aléatoire que la graine. Utilisez crypto.getRandomValues(new Uint32Array(1))[0] à la place.
Utiliser Date.now() comme entropie : le temps est devinable dans des fenêtres étroites. Même combiné avec un petit facteur aléatoire, les horodatages fuient suffisamment de bits pour les attaquants.
Construire votre propre source en XOR-ant des sources : n'essayez pas. Les CSPRNG du système d'exploitation mélangent déjà toutes les sources d'entropie utiles. Ajouter votre propre brassage réduit généralement l'entropie plutôt que de l'augmenter.
Biais modulo lors de la génération de plages : randomBytes[0] % 10 n'est pas uniformément distribué sur 0-9 car 256 n'est pas un multiple de 10. Pour des entiers aléatoires uniformes dans une plage, utilisez le rejet par échantillonnage :
function randomInt(max) {
const range = new Uint32Array(1);
const threshold = 2**32 - (2**32 % max);
do {
crypto.getRandomValues(range);
} while (range[0] >= threshold);
return range[0] % max;
}
Sources d'entropie et préoccupations au démarrage
Sur Linux, /dev/urandom est toujours sûr après le démarrage initial. Pendant les premières secondes du démarrage sur des systèmes sans RNG matériel, le pool noyau peut être insuffisamment alimenté. Cela a été exploité dans le bug Debian OpenSSL de 2008 où un patch avait supprimé le mélange d'entropie, ne laissant que l'ID de processus comme graine. Les clés générées dans cette fenêtre n'avaient que 2^15 valeurs possibles — énumérées en secondes.
Les systèmes modernes alimentent le CSPRNG noyau à partir de : RDSEED sur x86-64 (quand disponible), instructions RNG ARMv8.5-A, bruit thermique de divers périphériques, timing des interruptions, clavier/souris si interactif. Sur les serveurs utilisant du matériel comme Intel Ice Lake ou AMD Zen 3+, le CSPRNG est alimenté dans les microsecondes suivant le démarrage.
Pour les conteneurs Docker : /dev/urandom de l'hôte est transmis par défaut. Aucune action nécessaire. Pour le serverless (AWS Lambda, Cloudflare Workers), le runtime gère l'alimentation en entropie par invocation.
Jetons de session et identifiants de partage
Pour un service de transfert de fichiers, vous générez des identifiants aléatoires pour :
- Les identifiants de fichiers dans les URL (les attaquants ne devraient pas pouvoir deviner les ID valides)
- Les jetons de partage pour les liens protégés par mot de passe
- Les jetons CSRF
- Les clés de chiffrement (clés AES par fichier)
- Les nonces pour GCM
Longueur minimale : 128 bits (16 octets) pour la résistance aux collisions et à la devinette, 256 bits (32 octets) pour les clés. L'encodage URL-sûr via base64url ajoute ~33 % de longueur ; l'hexadécimal ajoute 100 %.
Un jeton base64url encodé de 32 octets fait 43 caractères et est essentiellement sans collision à 2^256.
Tester un RNG faible
Signes que votre RNG est cassé ou faible :
- Jetons identiques générés par différentes requêtes (collision dans un espace qui devrait être immense)
- La sortie passe les tests visuels mais échoue aux batteries statistiques
dieharderouPractRand - Réutilisation de la graine après redémarrage du processus — chaque déploiement utilise le même état initial
- Les clés générées tombent dans des schémas (par ex. les 4 premiers octets varient mais les 28 derniers sont identiques)
En production, vous ne verrez probablement pas ces signes sauf si quelque chose est catastrophiquement faux. Le mode d'échec est généralement silencieux : les attaques deviennent simplement pratiques sur ce qui devrait être un espace de recherche de 2^256.
Audit : chaque appel à Math.random() dans une base de code doit être examiné. Un grep pour Math.random dans votre arbre source est une bonne vérification hebdomadaire d'hygiène. Convertir tout site d'appel lié à la sécurité vers crypto.getRandomValues prend quelques minutes et prévient de vraies vulnérabilités.
Chaînes aléatoires et UUID
Pour les identifiants destinés aux humains, crypto.randomUUID() retourne un UUID v4 (122 bits d'aléatoire) dans un format standard :
const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
Supporté dans Chrome 92+, Firefox 95+, Safari 15.4+, Node 14.17+. Bon pour les clés primaires de base de données, les identifiants de requêtes API, et les identifiants uniques non critiques pour la sécurité. Utilisez getRandomValues explicite pour tout ce qui nécessite des formats personnalisés ou une entropie plus élevée.
Sur les serveurs : évitez les pools RNG personnalisés
Certains frameworks serveur offrent leurs propres pools aléatoires qui prétendent « mélanger » le CSPRNG système avec de l'entropie au niveau applicatif. Traitez cela avec méfiance. Le mélange personnalisé améliore rarement la sortie du noyau et peut silencieusement réduire l'entropie si buggé.
Si vous êtes sur Node ou un runtime majeur, le crypto.randomBytes intégré est correct et rapide. Ne le remplacez pas par des mixeurs tiers.
L'essentiel
Chaque pièce de la cryptographie dans une application de transfert de fichiers dépend d'octets aléatoires imprévisibles. Les clés AES, les nonces GCM, les sels PBKDF2, les jetons de partage, les jetons anti-CSRF, les identifiants de session — tous ont besoin de la même primitive : crypto.getRandomValues() dans les navigateurs, crypto.randomBytes() ou webcrypto.getRandomValues dans Node. Utilisez ceux-là. Jamais Math.random(). Jamais des horodatages. Jamais votre propre mixeur.
HexaTransfer dérive chaque clé AES par fichier, nonce et identifiant d'URL depuis crypto.getRandomValues() sur le client. Les identifiants de partage côté serveur proviennent de crypto.randomBytes. Une API, un comportement cohérent, aucune façon d'introduire accidentellement des bits prévisibles dans le système.
La primitive est ennuyeuse précisément parce qu'elle doit l'être. Ennuyeuse, correcte et disponible partout — exactement ce que les fondements de la crypto doivent être.
Essayez sur hexatransfer.com — gratuit, sans compte, jusqu'à 10 Go.
Envoyez vos fichiers volumineux en toute sécurité avec le chiffrement de bout en bout
Transférez des fichiers jusqu'à 10 Go gratuitement avec le chiffrement de bout en bout. Aucun compte requis. Vos fichiers sont chiffrés dans votre navigateur avant l'envoi — personne d'autre ne peut les lire.
Envoyer un fichier