Se stiamo parlando della Oracle (nata Sun) l'attuazione di java.util.Random
, allora sì, è possibile una volta che conosci abbastanza bit.
Random
utilizza un seme a 48 bit e un generatore congruenziale lineare. Questi non sono generatori crittograficamente sicuri, a causa delle minuscole dimensioni dello stato (bruteforceable!) E del fatto che l'output non è casuale (molti generatori esibiranno una piccola lunghezza del ciclo in alcuni bit, il che significa che questi bit possono essere facilmente predetti anche se gli altri bit sembrano casuali). aggiornamento seme
Random
's è la seguente:
nextseed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
Questa è una funzione molto semplice, e può essere invertito se si conosce tutti i bit del seme calcolando
seed = ((nextseed - 0xBL) * 0xdfe05bcb1365L) & ((1L << 48) - 1)
dal 0x5DEECE66DL * 0xdfe05bcb1365L = 1
mod 2 . Con questo, un singolo valore di seme in qualsiasi momento è sufficiente per recuperare tutti i semi passati e futuri.
Random
non ha funzioni che rivelano l'intero seme, però, quindi dovremo essere un po 'intelligenti.
Ora, ovviamente, con un seme a 48 bit, è necessario osservare almeno 48 bit di output o chiaramente non si dispone di una funzione iniettiva (e quindi invertibile) con cui lavorare. Siamo fortunati: nextLong
restituisce ((long)(next(32)) << 32) + next(32);
, quindi produce 64 bit di output (più del necessario). In effetti, potremmo probabilmente accontentarci di nextDouble
(che produce 53 bit) o semplicemente di chiamate ripetute di qualsiasi altra funzione. Si noti che queste funzioni possono non emette più di 2 valori unici a causa delle limitate dimensioni del seme (quindi, per esempio, ci sono 2 -2 long
s che nextLong
sarà mai produrre).
Diamo un'occhiata in particolare a nextLong
. Restituisce il numero (a << 32) + b
dove a
e sono entrambe quantità a 32 bit. Lasciare s
essere il seme prima che venga chiamato nextLong
. Quindi, lasciare t = s * 0x5DEECE66DL + 0xBL
, in modo che a
sia i 32 bit alti di t
e lasciare u = t * 0x5DEECE66DL + 0xBL
in modo che b
siano i 32 bit alti di u
. Sia c
e d
i 16 bit bassi di t
e u
rispettivamente.
Si noti che dal c
e dal d
sono quantità di 16 bit, possiamo solo rinforzarli (poiché ne abbiamo solo bisogno) e utilizzarli. È piuttosto economico, dal momento che il numero 2 è solo 65536 - piccolo per un computer.Ma siamo un po 'più intelligenti e vediamo se c'è un modo più veloce.
Abbiamo (b << 16) + d = ((a << 16) + c) * 0x5DEECE66DL + 11
. Così, facendo qualche algebra, otteniamo (b << 16) - 11 - (a << 16)*0x5DEECE66DL = c*0x5DEECE66DL - d
, mod 2 . Poiché c
e d
sono entrambe le quantità a 16 bit, c*0x5DEECE66DL
ha al massimo 51 bit. Ciò significa utilmente che
(b << 16) - 11 - (a << 16)*0x5DEECE66DL + (k<<48)
è pari a c*0x5DEECE66DL - d
per qualche k
al massimo 6. (Ci sono modi più sofisticati per calcolare c
e d
, ma perché il legato sulla k
è così piccolo, è più facile da solo bruteforce) .
Possiamo verificare appena tutti i valori possibili per k
fino ad ottenere un valore whos negati restante mod 0x5DEECE66DL
è di 16 bit (mod 2 nuovo), in modo da recuperare i 16 bit inferiori di entrambi t
e u
. A quel punto, abbiamo un seme completo, quindi possiamo trovare i semi futuri usando la prima equazione o i semi passati usando la seconda equazione.
codice che illustrano l'approccio:
import java.util.Random;
public class randhack {
public static long calcSeed(long nextLong) {
final long x = 0x5DEECE66DL;
final long xinv = 0xdfe05bcb1365L;
final long y = 0xBL;
final long mask = ((1L << 48)-1);
long a = nextLong >>> 32;
long b = nextLong & ((1L<<32)-1);
if((b & 0x80000000) != 0)
a++; // b had a sign bit, so we need to restore a
long q = ((b << 16) - y - (a << 16)*x) & mask;
for(long k=0; k<=5; k++) {
long rem = (x - (q + (k<<48))) % x;
long d = (rem + x)%x; // force positive
if(d < 65536) {
long c = ((q + d) * xinv) & mask;
if(c < 65536) {
return ((((a << 16) + c) - y) * xinv) & mask;
}
}
}
throw new RuntimeException("Failed!!");
}
public static void main(String[] args) {
Random r = new Random();
long next = r.nextLong();
System.out.println("Next long value: " + next);
long seed = calcSeed(next);
System.out.println("Seed " + seed);
// setSeed mangles the input, so demangle it here to get the right output
Random r2 = new Random((seed^0x5DEECE66DL) & ((1L << 48)-1));
System.out.println("Next long value from seed: " + r2.nextLong());
}
}
Che cosa vuoi dire? (Non per essere offensivo, ma non sono sicuro che tu conosca il significato di 'funzione inversa ') –
@OneTwoThree Il nome corretto per esso è la funzione inversa - Non ho mai sentito" reverse "prima. Questo potrebbe essere il motivo per cui c'è un po 'di confusione da @LukasKnuth – ddmps
questo è correlato alla stampa di "ciao mondo" con semi di numeri casuali. http://stackoverflow.com/questions/15182496/why-does-this-code-print-hello-world –