Generazione numeri casuali sicuri: fondamento della crittografia forte
La generazione casuale sicura è critica per la crittografia. Come funziona crypto.getRandomValues e perché la casualità debole rompe la sicurezza.
La generazione sicura di numeri casuali è il fondamento su cui poggia ogni altro elemento della crittografia. In JavaScript, crypto.getRandomValues(buffer) riempie un TypedArray con byte casuali crittograficamente sicuri provenienti dal CSPRNG del sistema operativo (/dev/urandom su Linux/macOS, BCryptGenRandom su Windows, SecRandomCopyBytes su iOS/macOS). Non usare Math.random() per nulla che riguardi la sicurezza — è un PRNG in stile Mulberry32 o xorshift progettato per la velocità, non per l'imprevedibilità, e il suo output è prevedibile dopo aver osservato pochi campioni. Un RNG debole compromette le chiavi AES, gli handshake TLS, l'unicità dei nonce in GCM, la non-indovinabilità dei token e ogni altro primitivo di sicurezza che dipende da bit imprevedibili.
La differenza tra casuale e casuale crittografico
Un PRNG (generatore di numeri pseudo-casuali) produce un flusso deterministico da un seed. Dati il seed e l'algoritmo, puoi riprodurre ogni output. Va bene per giochi, simulazioni e metodi Monte Carlo. Catastrofico per la crittografia.
Un CSPRNG (PRNG crittograficamente sicuro) è seminato da una sorgente di vera entropia (rumore termico, timing degli interrupt, istruzioni hardware RNG come Intel RDSEED) e il suo design garantisce che l'output sia computazionalmente indistinguibile dalla vera casualità, e osservare l'output precedente non aiuta a predire quello futuro.
JavaScript ti dà entrambi. Math.random() è un PRNG. crypto.getRandomValues() è un wrapper sul CSPRNG del sistema operativo. Una riga di codice di differenza, un'enorme differenza di sicurezza.
L'uso corretto canonico
// Genera byte casuali per una chiave AES a 256 bit
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
// Genera un nonce GCM a 96 bit
const nonce = crypto.getRandomValues(new Uint8Array(12));
// Genera un salt a 128 bit
const salt = crypto.getRandomValues(new Uint8Array(16));
// Genera un token casuale URL-safe
const tokenBytes = crypto.getRandomValues(new Uint8Array(32));
const token = btoa(String.fromCharCode(...tokenBytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
crypto.getRandomValues() è sincrono, riempie il buffer in loco e restituisce il buffer. La dimensione massima della richiesta è 65.536 byte in una singola chiamata (una quota imposta dalla specifica per evitare il blocco). Per materiale casuale di dimensioni maggiori, chiama ripetutamente.
Equivalenti Node.js
const { randomBytes, randomFillSync, webcrypto } = require('crypto');
const keyBytes = randomBytes(32); // Restituisce un Buffer
// Oppure compatibile con Web Crypto
const nonce = webcrypto.getRandomValues(new Uint8Array(12));
Il randomBytes di Node attinge dallo stesso CSPRNG sottostante di Web Crypto. Usa qualsiasi stile di API si adatti al tuo codice. Per codice isomorfico che gira in entrambi gli ambienti, webcrypto.getRandomValues corrisponde esattamente al browser.
Perché Math.random non funziona
V8 (Chrome/Node), SpiderMonkey (Firefox) e JavaScriptCore (Safari) implementano tutti Math.random() come un PRNG veloce senza garanzie crittografiche. V8 usa una variante xorshift128+. I ricercatori hanno dimostrato che dopo aver osservato ~5 output, un attaccante può recuperare lo stato interno e predire tutti gli output futuri. Nel 2015, Mike Pound e colleghi hanno invertito lo stato Math.random di V8 in bug bounty reali.
Se usi Math.random() per generare token di sessione, link di reset password, nonce di cifratura o ID di condivisione, gli attaccanti che ne osservano alcuni possono predire tutti gli altri. Non è teorico — è una classe di bug comune negli audit.
Usi scorretti comuni
Seminare un PRNG di libreria con Math.random():
// SBAGLIATO
const seed = Math.floor(Math.random() * 2**32);
Nulla a valle può essere più casuale del seed. Usa invece crypto.getRandomValues(new Uint32Array(1))[0].
Usare Date.now() come entropia: il tempo è indovinabile entro finestre strette. Anche combinato con un piccolo fattore casuale, i timestamp fanno trapelare abbastanza bit per gli attaccanti.
Combinare sorgenti facendo XOR: non farlo. I CSPRNG del sistema operativo mescolano già ogni sorgente di entropia utile. Aggiungere il tuo mix tipicamente riduce l'entropia invece di aumentarla.
Bias di modulo nella generazione di intervalli: randomBytes[0] % 10 non è distribuito uniformemente su 0–9 perché 256 non è un multiplo di 10. Per interi casuali uniformi in un intervallo, usa il rejection sampling:
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;
}
Sorgenti di entropia e problemi al boot
Su Linux, /dev/urandom è sempre sicuro dopo i primi istanti di boot. Durante i primi secondi di avvio su sistemi senza RNG hardware, il pool del kernel potrebbe essere sotto-seminato. Questo è stato sfruttato nel bug OpenSSL Debian del 2008, dove una patch aveva rimosso il mescolamento dell'entropia lasciando solo il PID del processo come seed. Le chiavi generate in quella finestra avevano solo 2^15 valori possibili — enumerati in secondi.
I sistemi moderni seminano il CSPRNG del kernel da: RDSEED su x86-64 (quando disponibile), istruzioni RNG ARMv8.5-A, rumore termico da varie periferiche, timing degli interrupt, tastiera/mouse se interattivo. Su server con Intel Ice Lake o AMD Zen 3+, il CSPRNG è seminato entro microsecondi dal boot.
Per i container Docker: il /dev/urandom dell'host viene passato per default. Nessuna azione necessaria. Per serverless (AWS Lambda, Cloudflare Workers), il runtime gestisce il seeding dell'entropia per ogni invocazione.
Token di sessione e ID di condivisione
Per un servizio di trasferimento file, generi identificatori casuali per:
- ID file negli URL (gli attaccanti non devono poter indovinare ID validi)
- Token di condivisione per link protetti da password
- Token CSRF
- Chiavi di cifratura (chiavi AES per-file)
- Nonce per GCM
Lunghezza minima: 128 bit (16 byte) per collisione e non-indovinabilità, 256 bit (32 byte) per le chiavi. La codifica URL-safe in base64url aggiunge ~33% di lunghezza; hex aggiunge il 100%.
Un token codificato in base64url a 32 byte è di 43 caratteri ed è essenzialmente privo di collisioni a 2^256.
Verificare la debolezza dell'RNG
Segnali che il tuo RNG è rotto o debole:
- Token identici generati da richieste diverse (collisione in quello che dovrebbe essere uno spazio enorme)
- L'output supera i test visivi ma fallisce le batterie statistiche
dieharderoPractRand - Riutilizzo del seed dopo il riavvio del processo — ogni deploy usa lo stesso stato iniziale
- Le chiavi generate cadono in pattern (es. i primi 4 byte variano ma gli ultimi 28 sono identici)
In produzione, probabilmente non lo vedrai a meno che qualcosa non sia catastroficamente sbagliato. La modalità di fallimento è di solito silenziosa: gli attacchi diventano semplicemente fattibili su quello che dovrebbe essere uno spazio di ricerca di 2^256.
Audit: ogni chiamata a Math.random() in un codebase dovrebbe essere rivista. Un grep per Math.random sull'albero dei sorgenti è un buon controllo di igiene settimanale. Convertire qualsiasi call site rilevante per la sicurezza a crypto.getRandomValues richiede minuti e previene vulnerabilità reali.
Stringhe casuali e UUID
Per gli identificatori rivolti all'utente, crypto.randomUUID() restituisce un UUID v4 (122 bit di casualità) in un formato standard:
const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
Supportato in Chrome 92+, Firefox 95+, Safari 15.4+, Node 14.17+. Utile per chiavi primarie di database, ID di richiesta API e identificatori unici non critici per la sicurezza. Usa getRandomValues esplicito per tutto ciò che richiede formati personalizzati o entropia più alta.
Sui server: evita i pool RNG personalizzati
Alcuni framework server offrono i propri pool casuali che affermano di "mescolare" il CSPRNG di sistema con entropia a livello applicativo. Trattali con sospetto. Il mescolamento personalizzato raramente migliora l'output del kernel e può silenziosamente ridurre l'entropia se è buggato.
Se sei su Node o un runtime importante, il crypto.randomBytes integrato è corretto e veloce. Non sostituirlo con mixer di terze parti.
Il punto fondamentale
Ogni elemento di crittografia in un'app di trasferimento file dipende da byte casuali imprevedibili. Chiavi AES, nonce GCM, salt PBKDF2, token di condivisione, token anti-CSRF, ID di sessione — tutti hanno bisogno dello stesso primitivo: crypto.getRandomValues() nei browser, crypto.randomBytes() o webcrypto.getRandomValues in Node. Usa quelli. Mai Math.random(). Mai i timestamp. Mai il tuo mixer.
HexaTransfer deriva ogni chiave AES per-file, ogni nonce e ogni identificatore URL da crypto.getRandomValues() sul client. Gli ID di condivisione lato server provengono da crypto.randomBytes. Un'unica API, comportamento coerente, nessuna possibilità di far trapelare accidentalmente bit prevedibili nel sistema.
Il primitivo è noioso proprio perché deve esserlo. Noioso, corretto e disponibile ovunque — esattamente ciò che i fondamenti della crittografia devono essere.
Provalo su hexatransfer.com — gratis, senza registrazione, fino a 10 GB.
Invia file di grandi dimensioni in modo sicuro con crittografia end-to-end
Trasferisci file fino a 10 GB gratuitamente con crittografia end-to-end. Nessun account necessario. I tuoi file vengono crittografati nel browser prima del caricamento — nessun altro può leggerli.
Invia un file