Java的Random函数接受一个种子并产生一系列'伪随机'数字。
(它是基于Donald Knuth, The Art of Computer Programming, Volume 3, Section 3.2.1.)
中讨论的一些算法实现的,但是这篇文章对我来说太技术了解无法理解)
它有反函数吗? 也就是说,给定一系列数字,是否有可能在数学上确定种子将是什么? (,这意味着,强制执行不算作有效的方法)
[编辑] 这里似乎有很多评论......我想我会澄清我在寻找什么。
例如,函数y = f(x) = 3x
具有反函数,即y = g(x) = x/3
。
但是函数z = f(x, y) = x * y
没有反函数,因为(我可以在这里给出完整的数学证明,但我不想偏离我的主要问题),直觉上说,不止一个一对(x, y)
,(x * y) == z
。
现在回到我的问题,如果你说这个功能不可逆,请解释原因。
(我希望从那些真正阅读过文章并理解它的人那里得到答案。像“这是不可能的”这样的答案并没有真正起作用)
答案 0 :(得分:21)
如果我们谈论java.util.Random
的Oracle(néeSun)实现,那么是的,一旦你知道了足够的位,就有可能。
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 s nextLong
永远不会产生。)
让我们具体看一下nextLong
。它返回一个(a << 32) + b
数字,其中a
和b
都是32位数量。在s
被调用之前,让nextLong
成为种子。然后,设t = s * 0x5DEECE66DL + 0xBL
,a
为t
的高32位,让u = t * 0x5DEECE66DL + 0xBL
使b
为u
的高32位1}}。让c
和d
分别为t
和u
的低16位。
请注意,由于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)
对于某些c*0x5DEECE66DL - d
,最多等于k
6。(有更复杂的方法来计算c
和d
,但因为{{1如此微小,更容易暴力)。
我们可以测试k
的所有可能值,直到我们得到一个值为否定余数mod k
为16位(mod 2 48 ),这样我们恢复了0x5DEECE66DL
和t
的低16位。那时,我们有一个完整的种子,所以我们可以使用第一个方程找到未来的种子,或者使用第二个方程找到过去的种子。
演示该方法的代码:
u
答案 1 :(得分:4)
我通常不会直接链接文章......但我找到了一个网站,有人在某种程度上深入研究这个问题并认为值得发帖。 http://jazzy.id.au/default/2010/09/20/cracking_random_number_generators_part_1.html
您似乎可以通过这种方式计算种子:
seed = (seed * multiplier + addend) mod (2 ^ precision)
其中乘数为25214903917,加数为11,精度为48(位)。你不能只用1个数字来计算种子是什么,但你可以用2来计算。
编辑:正如nhahtdh所说的那样,第2部分他将深入研究种子背后的数学。答案 2 :(得分:2)
我想提出一个实现来反转由nextInt()
生成的整数序列。
该程序将对nextInt()
丢弃的低16位进行暴力破解,使用James Roper在博客中提供的算法查找以前的种子,然后检查48位种子的高32位是否为与之前的号码相同。我们至少需要 2 整数来导出前一个种子。否则,前一个种子将有2个 16 的可能性,并且在我们至少再有一个种子之前,它们都是同等有效的。
它可以很容易地扩展到nextLong()
,而 1 long
数字足以找到种子,因为我们有2个上部32位的种子在一个long
,due to the way it is generated。
请注意,在某些情况下,结果与您在SEED
变量中设置为秘密种子的结果不同。如果您设置为秘密种子的数量占用超过48位(这是用于在内部生成随机数的位数),则long
中64位的高16位将被删除{{{ 1}}方法。在这种情况下,返回的结果将与您最初设置的结果不同,较低的48位可能是相同的。
我想给予this blog article的作者James Roper最大的荣誉,它使下面的示例代码成为可能:
setSeed()