Differenza tra java.util.Random e java.security.SecureRandom


202

Il mio team ha ricevuto un codice lato server (in Java) che genera token casuali e ho una domanda riguardo allo stesso -

Lo scopo di questi token è abbastanza sensibile - usato per ID sessione, collegamenti per reimpostare password ecc. Quindi devono essere crittograficamente casuali per evitare che qualcuno li indovini o li costringa brutalmente. Il token è un "lungo", quindi è lungo 64 bit.

Il codice attualmente utilizza la java.util.Randomclasse per generare questi token. La documentazione per java.util.Randomindica chiaramente quanto segue:

Le istanze di java.util.Random non sono crittograficamente sicure. Prendi invece in considerazione l'utilizzo di SecureRandom per ottenere un generatore di numeri pseudo-casuali sicuro crittograficamente per l'utilizzo da parte di applicazioni sensibili alla sicurezza.

Tuttavia, il modo in cui il codice utilizza attualmente java.util.Randomè questo: crea un'istanza della java.security.SecureRandomclasse e quindi utilizza il SecureRandom.nextLong()metodo per ottenere il seme utilizzato per creare un'istanza della java.util.Randomclasse. Quindi utilizza il java.util.Random.nextLong()metodo per generare il token.

Quindi la mia domanda ora: è ancora insicuro dato che java.util.Randomviene utilizzato il seeding java.security.SecureRandom? Devo modificare il codice in modo che venga utilizzato java.security.SecureRandomesclusivamente per generare i token?

Attualmente il seed del codice è Randomuna volta all'avvio


14
Una volta eseguito il seeding, l'output da java.util.Random è una sequenza deterministica di numeri. Potresti non volerlo.
Peter Štibraný,

1
Il codice Randomesegue il seeding una volta all'avvio oppure ne esegue uno nuovo per ogni token? Spero che questa sia una domanda stupida, ma ho pensato di controllare.
Tom Anderson,

8
Casuale ha solo uno stato interno a 48 bit e si ripeterà dopo 2 ^ 48 chiamate a nextLong (), il che significa che non produrrà tutti i valori longo doublevalori possibili .
Peter Lawrey,

3
C'è un altro grave problema. 64 bit significa 1,84 * 10 ^ 19 combinazioni possibili che sono troppo poche per resistere a un attacco sofisticato. Ci sono macchine là fuori che hanno decifrato un codice DES a 56 bit (fattore 256 in meno) con 90 * 10 ^ 9 chiavi al secondo in 60 ore. Usa 128 bit o due long!
Thorsten S.

Risposte:


232

L'implementazione standard di Oracle JDK 7 utilizza quello che viene chiamato un generatore congruenziale lineare per produrre valori casuali java.util.Random.

Tratto dal java.util.Randomcodice sorgente (JDK 7u2), da un commento sul metodo protected int next(int bits), che è quello che genera i valori casuali:

Questo è un generatore di numeri pseudocasuale congruenziale lineare, come definito da DH Lehmer e descritto da Donald E. Knuth in The Art of Computer Programming, Volume 3: Seminumerical Algorithms , sezione 3.2.1.

Prevedibilità dei generatori congruenziali lineari

Hugo Krawczyk ha scritto un articolo abbastanza buono su come si possono prevedere questi LCG ("Come prevedere i generatori congruenziali"). Se sei fortunato e interessato, potresti comunque trovarne una versione gratuita e scaricabile sul Web. E ci sono molte altre ricerche che dimostrano chiaramente che non dovresti mai usare un LCG per scopi critici per la sicurezza. Questo significa anche che i tuoi numeri casuali sono prevedibili in questo momento, qualcosa che non vuoi per ID di sessione e simili.

Come rompere un generatore congruenziale lineare

L'ipotesi che un utente malintenzionato debba attendere la ripetizione dell'LCG dopo un ciclo completo è errata. Anche con un ciclo ottimale (il modulo m nella sua relazione di ricorrenza) è molto facile prevedere i valori futuri in molto meno tempo di un ciclo completo. Dopotutto, è solo una serie di equazioni modulari che devono essere risolte, il che diventa semplice non appena si osservano abbastanza valori di output dell'LCG.

La sicurezza non migliora con un seme "migliore". Semplicemente non importa se si semina con un valore casuale generato da SecureRandomo addirittura si produce il valore tirando un dado più volte.

Un attaccante calcolerà semplicemente il seme dai valori di output osservati. Questo richiede significativamente meno tempo di 2 ^ 48 nel caso di java.util.Random. I miscredenti possono provare questo esperimento , in cui è dimostrato che è possibile prevedere Randomoutput futuri osservando solo due (!) Valori di output nel tempo di circa 2 ^ 16. Non ci vuole nemmeno un secondo su un computer moderno per prevedere l'output dei tuoi numeri casuali in questo momento.

Conclusione

Sostituisci il tuo codice attuale. Utilizzare SecureRandomesclusivamente Quindi almeno avrai una piccola garanzia che il risultato sarà difficile da prevedere. Se vuoi le proprietà di un PRNG crittograficamente sicuro (nel tuo caso, è quello che vuoi), allora devi SecureRandomsolo andare . Essere intelligenti nel cambiare il modo in cui avrebbe dovuto essere usato porterà quasi sempre a qualcosa di meno sicuro ...


4
Molto utile, forse potresti anche spiegare come funziona SecureRandom (proprio come spieghi come funziona Random) ..
gresdiplitude,

4
Ciò sconfigge lo scopo di secureRandom
Azulflame il

Lo so, ho imparato quella lezione nel modo più duro. Ma un codice cifrato e una fonte difficile da trovare funzionano bene. Notch potrebbe imparare qualcosa al riguardo (codifica la password del suo utente in un file .lastlogin, codificato con crittografia di base usando "passwordfile" come chiave)
Azulflame,

1
La vera domanda qui: se java è in grado di produrre un prng più sicuro con un'API simile, perché non hanno semplicemente sostituito quello rotto?
Joel Coehoorn,

11
@JoelCoehoorn Non Randomè rotto, ma dovrebbe essere usato solo in diversi scenari. Ovviamente, puoi sempre usare SecureRandom. Ma in generale, SecureRandomè notevolmente più lento del puro Random. E ci sono casi in cui sei interessato solo a buone proprietà statistiche e prestazioni eccellenti, ma non ti interessa davvero la sicurezza: le simulazioni Monte-Carlo sono un buon esempio. Ho fatto commenti a riguardo in una risposta simile , forse lo troverai utile.
rilievo

72

Un random ha solo 48 bit in cui SecureRandom può avere fino a 128 bit. Quindi le possibilità di ripetizione in securerandom sono molto piccole.

Casuale usa system clockcome seme / o per generare il seme. Quindi possono essere riprodotti facilmente se l'attaccante conosce l'ora in cui il seme è stato generato. Ma SecureRandom prende Random Datadal tuo os(possono essere intervalli tra sequenze di tasti ecc. - la maggior parte dei sistemi operativi raccoglie questi dati archiviandoli in file - /dev/random and /dev/urandom in case of linux/solaris) e li usa come seme.
Pertanto, se la dimensione del token piccola è corretta (nel caso di Casuale), puoi continuare a utilizzare il codice senza alcuna modifica, poiché stai utilizzando SecureRandom per generare il seed. Ma se vuoi token più grandi (che non possono essere soggetti brute force attacks) vai con SecureRandom -
In caso di 2^48tentativi casuali sono necessari, con CPU avanzate di oggi è possibile romperlo in tempo pratico. Ma per i 2^128tentativi di sicurezza più richiesti saranno necessari anni e anni per arrivare in pareggio con le macchine avanzate di oggi.

Vedi questo link per maggiori dettagli.
MODIFICA
Dopo aver letto i collegamenti forniti da @emboss, è chiaro che il seed, per quanto casuale possa essere, non dovrebbe essere usato con java.util.Random. È molto facile calcolare il seme osservando l'output.

Scegli SecureRandom : usa PRNG nativo (come indicato nel link sopra) perché accetta valori casuali dal /dev/randomfile per ogni chiamata anextBytes(). In questo modo un utente malintenzionato che osserva l'output non sarà in grado di distinguere nulla a meno che non stia controllando il contenuto del /dev/randomfile (il che è molto improbabile)
L' algoritmo sha1 prng calcola il seed solo una volta e se la VM è in esecuzione da mesi usando lo stesso seme, potrebbe essere rotto da un attaccante che sta osservando passivamente l'output.

NOTA - Se si chiama nextBytes()più velocemente di quanto il sistema operativo sia in grado di scrivere byte casuali (entropia) nel /dev/random, è possibile che si verifichino problemi quando si utilizza NATIVE PRNG . In tal caso, utilizzare un'istanza SHA1 PRNG di SecureRandom e ogni pochi minuti (o qualche intervallo), eseguire il seeding di questa istanza con il valore danextBytes()di un'istanza di PRNG NATIVE di SecureRandom. L'esecuzione in parallelo di questi due ti garantirà di eseguire regolarmente il seeding con valori casuali reali, senza esaurire l'entropia ottenuta dal sistema operativo.


Richiede molto meno di 2 ^ 48 per prevedere a Random, l'OP non dovrebbe usare Randomaffatto.
rilievo il

@emboss: sto parlando di bruteforce.
Ashwin,

1
Abbi cura di Linux: può raggiungere l'esaurimento dell'entropia (più nella VM che nell'hardware)! Guarda /proc/sys/kernel/random/entropy_availe verifica con alcuni dump del thread che non c'è troppo tempo di attesa durante la lettura/dev/random
Yves Martin,

2
Nota che Oracle JRE (almeno 1.7) funziona con / dev / urandom per impostazione predefinita e non / dev / random, quindi il suffisso della tua risposta non è più corretto. per verificare il controllo $ JAVA_HOME / lib / security / java.security per la proprietà securerandom.source
Boaz

1
Il nostro file java.security aveva securerandom.source = file: / dev / urandom invece del file: /// dev / urandom (due barre dopo i due punti per il protocollo del file, quindi un'altra barra per la radice del filesystem), facendone ricadere a / dev / random, che ha causato problemi di esaurimento del pool entropico. Impossibile modificarlo, quindi ho dovuto impostare una proprietà di sistema java.security.egd su quella giusta all'avvio dell'app.
maxpolk,

11

Se corri due volte java.util.Random.nextLong()con lo stesso seme, produrrà lo stesso numero. Per motivi di sicurezza che vuoi rispettare java.security.SecureRandomperché è molto meno prevedibile.

Le 2 classi sono simili, penso che hai solo bisogno di cambiamento Randomper SecureRandomuno strumento di refactoring e la maggior parte del codice esistente funzionerà.


11
Se prendi due istanze di un PRNG e lo esegui con lo stesso valore ottieni sempre gli stessi numeri casuali, anche usando SecureRandom non lo cambia. Tutti i PRNG sono deterministici e quindi prevedibili se si conosce il seme.
Robert,

1
Esistono diverse implementazioni SecureRandom, alcune sono PRNG, altre no. D'altra parte, java.util.Random è sempre PRNG (come definito nel suo Javadoc).
Peter Štibraný,

3

Se la modifica del codice esistente è un'attività economica, ti suggerisco di utilizzare la classe SecureRandom come suggerito in Javadoc.

Anche se trovi che l'implementazione della classe Random utilizza internamente la classe SecureRandom. non dovresti dare per scontato che:

  1. Altre implementazioni di VM fanno la stessa cosa.
  2. L'implementazione della classe Random nelle future versioni di JDK utilizza ancora la classe SecureRandom

Quindi è una scelta migliore seguire il suggerimento della documentazione e andare direttamente con SecureRandom.


Non credo che la domanda originale affermasse che l' java.util.Randomimplementazione utilizzata SecureRandominternamente, dicesse che il loro codice utilizzava SecureRandomper seminare il file Random. Tuttavia, sono d'accordo con entrambe le risposte finora; è meglio usare SecureRandomper evitare una soluzione esplicitamente deterministica.
Palpatim,

2

L'attuale implementazione di riferimento di java.util.Random.nextLong()effettua due chiamate al metodo next(int)che espone direttamente 32 bit del seed corrente:

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

I 32 bit superiori del risultato di nextLong()sono i bit del seme in quel momento. Poiché la larghezza del seme è di 48 bit (dice il javadoc), è sufficiente * scorrere i rimanenti 16 bit (sono solo 65.536 tentativi) per determinare il seme che ha prodotto il secondo 32 bit.

Una volta che il seme è noto, tutti i seguenti token possono essere facilmente calcolati.

Usando l'output di nextLong()direttamente, in parte il segreto del PNG a un livello tale che l'intero segreto può essere calcolato con pochissimo effetto. Pericoloso!

* È necessario uno sforzo se il secondo 32 bit è negativo, ma è possibile scoprirlo.


Corretta. Scopri come risolvere rapidamente java.util.random su jazzy.id.au/default/2010/09/20/… !
inghehere

2

Il seme non ha senso. Un buon generatore casuale si differenzia per il numero scelto. Ogni generatore casuale parte da un numero e scorre attraverso un "anello". Ciò significa che vieni da un numero all'altro, con il vecchio valore interno. Ma dopo un po 'si ricomincia dall'inizio e ricominciare tutto da capo. Quindi esegui cicli. (il valore restituito da un generatore casuale non è il valore interno)

Se si utilizza un numero primo per creare un anello, vengono scelti tutti i numeri in quell'anello, prima di completare un ciclo completo attraverso tutti i numeri possibili. Se si prendono numeri non primi, non tutti i numeri vengono scelti e si ottengono cicli più brevi.

Numeri primi più alti significano cicli più lunghi, prima di tornare di nuovo al primo elemento. Quindi, il generatore casuale sicuro ha solo un ciclo più lungo, prima di ricominciare da capo, ecco perché è più sicuro. Non è possibile prevedere la generazione di numeri con la stessa facilità dei cicli più brevi.

Con altre parole: devi sostituire tutto.


0

Cercherò di usare parole molto basilari in modo da poter comprendere facilmente la differenza tra Random e secureRandom e l'importanza della classe SecureRandom.

Vi siete mai chiesti come viene generato OTP (una volta password)? Per generare un OTP utilizziamo anche le classi Random e SecureRandom. Ora per rendere forte il tuo OTP, SecureRandom è meglio perché ci sono voluti 2 ^ 128 tentativi, per decifrare l'OTP che è quasi impossibile dalla macchina attuale ma se usato Random Class il tuo OTP può essere decifrato da qualcuno che può danneggiare i tuoi dati perché ha impiegato solo 2 ^ 48 prova, a crack.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.