如果我们谈论的是 Oracle (née Sun) 的实现java.util.Random
,那么是的,一旦您知道足够多的位,就有可能。
Random
使用 48 位种子和线性同余生成器。这些不是加密安全的生成器,因为状态大小很小(可暴力破解!)以及输出不是那么随机的事实(许多生成器在某些位中会表现出小的循环长度,这意味着即使这些位也可以很容易地预测如果其他位看起来是随机的)。
Random
的种子更新如下:
nextseed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
这是一个非常简单的函数,如果你通过计算知道种子的所有位,它就可以倒置
seed = ((nextseed - 0xBL) * 0xdfe05bcb1365L) & ((1L << 48) - 1)
从0x5DEECE66DL * 0xdfe05bcb1365L = 1
mod 2 48开始。有了这个,任何时间点的单个种子值都足以恢复所有过去和未来的种子。
Random
但是,它没有揭示整个种子的函数,所以我们必须有点聪明。
现在,显然,使用 48 位种子,您必须观察至少 48 位输出,否则您显然没有可使用的单射(因此是可逆的)函数。我们很幸运:nextLong
返回((long)(next(32)) << 32) + next(32);
,所以它产生了 64 位的输出(比我们需要的多)。事实上,我们可能会使用nextDouble
(它产生 53 位),或者只是重复调用任何其他函数。请注意,由于种子的大小有限,这些函数不能输出超过 2 48 个唯一值(因此,例如,有 2 64 -2 48 个 long
永远nextLong
不会产生)。
我们来具体看看nextLong
。它返回一个数字(a << 32) + b
,其中a
和b
都是 32 位量。Let s
be the seed beforenextLong
被调用。然后,让t = s * 0x5DEECE66DL + 0xBL
,a
是 的高 32 位t
,让u = t * 0x5DEECE66DL + 0xBL
,b
是 的高 32 位u
。令c
和分别为和d
的低 16 位。t
u
请注意,因为c
和d
是 16 位的量,我们可以强行使用它们(因为我们只需要一个)并完成它。这很便宜,因为 2 16只有 65536——对于计算机来说很小。但是让我们更聪明一点,看看是否有更快的方法。
我们有(b << 16) + d = ((a << 16) + c) * 0x5DEECE66DL + 11
. 因此,做一些代数,我们得到(b << 16) - 11 - (a << 16)*0x5DEECE66DL = c*0x5DEECE66DL - d
, mod 2 48。由于c
和d
都是 16 位的量,c*0x5DEECE66DL
最多有 51 位。这有用地意味着
(b << 16) - 11 - (a << 16)*0x5DEECE66DL + (k<<48)
最多等于6。(c*0x5DEECE66DL - d
有k
更复杂的方法来计算c
和d
,但由于边界k
非常小,因此蛮力更容易)。
我们可以测试所有可能的值,k
直到我们得到一个取反的余数 mod0x5DEECE66DL
是 16 位的值(再次是 mod 2 48),这样我们就可以恢复 和 的低 16t
位u
。那时,我们有一个完整的种子,所以我们可以使用第一个方程找到未来的种子,或者使用第二个方程找到过去的种子。
演示该方法的代码:
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());
}
}