关于Java:有没有办法从一系列数字中生成种子?

Is there a way to generate a seed out of a sequence of numbers?

例如,如果Java产生伪随机序列:9 3,2,5,6。用23作为种子,我该怎么做呢?即从序列9 3 2 5 6中取出23。

或者如何为某个序列分配种子?

如果有一个数据库,很容易做到-只需为序列分配一个随机键

1
INSERT INTO SEQUENCE_TABLE VALUES (RANDOM_KEY, SEQUENCE)

但是,如果不允许我使用数据库,有没有一个公式可以做这样的事情?


随机数生成器的要点是这是不可能的。SecureRandom在密码学上特别强大,但一般来说,如果您正在编写一个随机数生成器,而这是可能的或很容易的,那么您就错了。

也就是说,Java内置的随机类很可能是不可能的。(不过,SecureRandom是另一回事。)但它需要惊人的数学运算量。

更具体一点:如果存在一个多项式时间算法来做你想做的事情,对于某个特定的伪随机数生成器,那么根据定义,它将无法通过链接的维基百科文章中描述的"下一位测试",因为你可以预测将要生成的下一个元素。


是的,逆向设计一个设计不好的伪随机数发生器的数字流是绝对容易的,例如Java编程语言中的线性同余PRNG实现(EDOCX1,1)。

事实上,只有来自特定生成器的两个值,以及这些值出现的顺序信息,就可以预测整个流。

1
2
3
4
5
6
7
8
9
10
Random random = new Random();
long v1 = random.nextInt();
long v2 = random.nextInt();
for (int i = 0; i < 65536; i++) {
    long seed = v1 * 65536 + i;
    if (((seed * multiplier + addend) & mask) >>> 16) == v2) {
        System.out.println("Seed found:" + seed);
        break;
    }
}

这正是为什么使用密码安全的随机数生成器非常关键的原因,这些生成器已经被社区广泛审查,用于需要安全性的实现。

这里有更多关于逆向工程的信息,包括java.util.Random。…


当然可以恢复java.util.random使用的种子。这篇文章描述了Random线性同余公式背后的数学原理,这里有一个函数可以从nextint()返回的最后两个整数中发现当前种子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static long getCurrentSeed(int i1, int i2) {
        final long multiplier = 0x5DEECE66DL;
        final long inv_mult = 0xDFE05BCB1365L;
        final long increment = 0xBL;
        final long mask = ((1L << 48) - 1);

        long suffix = 0L;
        long lastSeed;
        long currSeed;
        int lastInt;

        for (long i=0; i < (1<<16); i++) {
                suffix = i;
                currSeed = ((long)i2 << 16) | suffix;
                lastSeed = ((currSeed - increment) * inv_mult) & mask;
                lastInt = (int)(lastSeed >>> 16);

                if (lastInt == i1) {
                        /* We've found the current seed, need to roll back 2 seeds */
                        currSeed = lastSeed;
                        lastSeed = ((currSeed - increment) * inv_mult) & mask;
                        return  lastSeed ^ multiplier;
                }
        }

        /* Error, current seed not found */
        System.err.println("current seed not found");
        return 0;
}

此函数返回一个值,该值可与rand.setseed()一起使用,生成以i1和i2开头的伪随机数字序列。


您想取任意的数字序列,然后确定一个短的(固定长度?)允许您重新生成该数字序列而不存储原始序列的键?不幸的是,你想要的在技术上是不可能的。这就是为什么:

这是压缩的一种特殊情况。您有一个很长的数据序列,您希望能够从一个较小的信息片段中重新创建无损的数据序列。如果您请求的是可能的,那么我可以将整个堆栈溢出压缩为一个整数(因为整个网站可以序列化为一个数字序列,尽管这个序列非常长!)

不幸的是,数学不是这样的。任何给定的序列都有一个特定的熵度量——该序列的平均复杂性。为了无损地重现这个序列,您必须能够编码至少足够的信息来表示它的熵。

对于某些序列,可能实际上有一个种子能够生成一个长的、特定的序列,但这仅仅是因为有一个硬编码的数学函数,它接受该种子并生成一个特定的数字序列。但是,要获取任意值序列并生成这样的种子,您需要一个种子,以及一个能够从该种子生成该序列的函数。为了对这两样东西进行编码,你会发现你得到的数据比预期的要多得多!


如果您可以使用String作为种子,则可以使用:

1
String seed ="9 3 2 5 6";

然后你的发电机看起来像:

1
String[] numbers = seed.split("");

如果你真的想在Java中逆向设计"随机数"生成器,那将是非常困难的(我想)。

如果可以的话,最好换一种方式来做:从种子开始,产生序列,然后从那里开始计算。