如果我们谈论的是Oracle(néeSun)
java.util.Random的实现,那么是的,一旦你知道足够的位就可以了。
随机使用48位种子和线性同余发生器。这些不是加密安全的生成器,因为微小的状态大小(可强制执行!)和输出不是随机的(许多发生器将在某些位显示小的周期长度,这意味着这些位可以很容易地预测甚至如果其他位看起来是随机的)。
随机的种子更新如下:
nextseed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
这是一个非常简单的功能,如果您通过计算知道种子的所有位,它可以反转
seed = ((nextseed - 0xBL) * 0xdfe05bcb1365L) & ((1L << 48) - 1)
因为0x5DEECE66DL * 0xdfe05bcb1365L = 1 mod 248.由此,任何时间点的单个种子值足以恢复所有过去和将来的种子。
随机没有显示整个种子的功能,所以我们必须要有点聪明。
现在显然,使用48位种子,你必须观察至少48位的输出,或者你显然没有注入(因此可逆)的功能来使用。我们运气好:nextLong返回((long)(next(32))< 32)next(32);所以它产生64位的输出(超过我们需要的)。的确,我们可以用nextDouble(产生53位)或者只是重复调用任何其他函数。请注意,由于种子的大小有限,因此这些函数不能输出超过248个唯一值(因此,例如,下一个Long永远不会产生264-248个long)。 我们来看看nextLong吧。它返回一个数字(a< 32)b,其中a和b都是32位数量。让我们在下一个龙之前成为种子。然后,让t = s * 0x5DEECE66DL 0xBL,这样a是t的高32位,令u = t * 0x5DEECE66DL 0xBL,使得b是u的高32位。令c和d分别为t和u的低16位。 请注意,由于c和d是16位数量,我们只能强制他们(因为我们只需要一个),并且完成它。这很便宜,因为216只是65536 – 一台电脑很小。但是让我们更聪明一些,看看是否有更快的方式。 我们有(b <16)d =((a< 16)c)* 0x5DEECE66DL 11.因此,做一些代数,我们得到(b <16)〜11 - (a< < 16)* 0x5DEECE66DL = c * 0x5DEECE66DL-d,mod 248.由于c和d都是16位数量,因此c * 0x5DEECE66DL最多有51位。这有用地意味着
(b << 16) - 11 - (a << 16)*0x5DEECE66DL + (k<<48)
等于c * 0x5DEECE66DL – d,对于某些k至多为6.(有更复杂的方法来计算c和d,但是由于k上的界限很小,所以只需要强力)。
我们可以测试k的所有可能的值,直到我们得到一个值,否定余数mod 0x5DEECE66DL是16位(mod 248再次),以便我们恢复t和u的低16位。在这一点上,我们有一个完整的种子,所以我们可以使用第一个方程找到未来的种子,或使用第二个方程过去的种子。
代码演示方法:
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());
}
}