218

我的团队收到了一些生成随机令牌的服务器端代码(Java),我对此有疑问 -

这些令牌的用途相当敏感 - 用于会话 ID、密码重置链接等。因此它们确实需要加密随机以避免有人猜测它们或暴力破解它们是可行的。令牌是“长”的,因此它是 64 位长。

该代码当前使用java.util.Random该类来生成这些标记。的文档java.util.Random清楚地说明了以下内容:

java.util.Random 的实例在密码学上不是安全的。考虑改为使用 SecureRandom 来获取加密安全的伪随机数生成器,以供安全敏感的应用程序使用。

但是,代码当前使用的方式java.util.Random是这样的 - 它实例化java.security.SecureRandom类,然后使用该SecureRandom.nextLong()方法获取用于实例化java.util.Random类的种子。然后它使用java.util.Random.nextLong()方法来生成令牌。

java.util.Random所以我现在的问题 - 考虑到正在使用 播种,它仍然不安全java.security.SecureRandom吗?我是否需要修改代码以使其java.security.SecureRandom专门用于生成令牌?

目前代码种子是Random启动时的一次

4

7 回答 7

247

标准的 Oracle JDK 7 实现使用所谓的线性同余生成器在java.util.Random.

取自java.util.Random源代码(JDK 7u2),来自对方法的注释,该方法protected int next(int bits)是生成随机值的方法:

这是一个线性同余伪随机数生成器,由 DH Lehmer 定义并由 Donald E. Knuth 在 计算机编程艺术,第 3 卷: 半数字算法,第 3.2.1 节中描述。

线性同余生成器的可预测性

Hugo Krawczyk 写了一篇关于如何预测这些 LCG 的非常好的论文(“如何预测同余生成器”)。如果您幸运且感兴趣,您仍然可以在网络上找到它的免费、可下载版本。还有更多研究清楚地表明,您永远不应该将 LCG 用于安全关键目的。这也意味着您的随机数现在可预测的,这是您不希望会话 ID 等的东西。

如何打破线性同余生成器

攻击者必须等待 LCG 在一个完整周期后重复的假设是错误的。即使使用最佳循环(其递推关系中的模数 m),也很容易在比完整循环更短的时间内预测未来值。毕竟,这只是一堆需要求解的模方程,只要你观察到足够多的 LCG 输出值,就变得容易了。

“更好”的种子不会提高安全性。SecureRandom如果您使用由骰子生成的随机值或什至通过多次掷骰子产生该值来播种,这根本无关紧要。

攻击者将简单地根据观察到的输出值计算种子。在. _ _ java.util.Random不信的人可以试试这个实验,它表明你可以预测未来的Random输出,只观察两个(!)输出值大约 2^16。在现代计算机上,现在甚至不需要一秒钟来预测随机数的输出。

结论

替换您当前的代码。SecureRandom独占使用。那么至少你会有一点保证,结果是很难预测的。如果您想要加密安全 PRNG 的属性(在您的情况下,这就是您想要的),那么您SecureRandom只需要使用即可。聪明地改变它应该使用的方式几乎总是会导致一些不太安全的东西......

于 2012-06-15T14:32:29.463 回答
77

随机数只有 48 位,而 SecureRandom 最多可以有 128 位。因此,在安全随机中重复的机会非常小。

随机使用system clock作为种子/或生成种子。因此,如果攻击者知道种子的生成时间,它们可以很容易地被复制。但是SecureRandomRandom Data从您那里获取os(它们可以是击键之间的间隔等 - 大多数操作系统收集这些数据并将它们存储在文件中 - /dev/random and /dev/urandom in case of linux/solaris)并将其用作种子。
因此,如果小令牌大小没问题(在 Random 的情况下),您可以继续使用您的代码而无需任何更改,因为您使用 SecureRandom 来生成种子。但是,如果您想要更大的令牌(不能受制于brute force attacks),请使用 SecureRandom -
如果需要随机2^48尝试,则使用当今先进的 cpu 可以在实际时间内打破它。但是为了安全2^128,将需要随机尝试,这将需要数年时间才能达到今天的先进机器的收支平衡。

有关更多详细信息,请参阅链接。
编辑
阅读@emboss提供的链接后,很明显,种子,无论它可能是随机的,都不应该与java.util.Random一起使用。通过观察输出很容易计算种子。

选择 SecureRandom - 使用本机 PRNG/dev/random (如上面的链接中所示),因为每次调用它都会从文件中获取随机值nextBytes(). 这样,除非他正在控制文件的内容(这不太可能) ,否则观察输出的攻击者将无法弄清任何内容/dev/random(这不太可能)sha1 prng算法仅计算一次种子,并且如果您的 VM 使用相同的程序运行了几个月种子,它可能被被动观察输出的攻击者破解。注意- 如果您调用的速度比您的操作系统能够将随机字节(熵)写入的速度更快,那么在使用NATIVE PRNG时您可能会遇到麻烦 。在这种情况下,使用 SecureRandom 的 SHA1 PRNG 实例,每隔几分钟(或某个时间间隔),使用来自


nextBytes()/dev/randomnextBytes()SecureRandom 的 NATIVE PRNG 实例。并行运行这两个将确保您使用真正的随机值定期播种,同时也不会耗尽操作系统获得的熵。

于 2012-06-16T03:27:20.827 回答
12

如果您java.util.Random.nextLong()使用相同的种子运行两次,它将产生相同的数字。出于安全原因,您要坚持使用,java.security.SecureRandom因为它的可预测性要低得多。

这 2 个类是相似的,我认为您只需要使用重构工具进行更改RandomSecureRandom您现有的大部分代码都可以工作。

于 2012-06-15T13:10:37.163 回答
3

如果更改现有代码是一项负担得起的任务,我建议您按照 Javadoc 中的建议使用 SecureRandom 类。

即使您发现 Random 类实现在内部使用 SecureRandom 类。你不应该想当然地认为:

  1. 其他虚拟机实现做同样的事情。
  2. JDK未来版本中Random类的实现仍然使用SecureRandom类

因此,遵循文档建议并直接使用 SecureRandom 是更好的选择。

于 2012-06-15T13:11:57.007 回答
2

的当前参考实现对直接公开 32 位当前种子的方法进行java.util.Random.nextLong()了两次调用:next(int)

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

结果的高 32 位是nextLong()当时种子的位。由于种子的宽度是 48 位(javadoc 说),因此只需迭代剩余的 16 位(仅尝试 65.536 次)即可确定产生第二个 32 位的种子。

一旦知道种子,就可以轻松计算所有后续令牌。

直接使用 PNG 的部分秘密的输出,nextLong()在某种程度上可以轻松计算整个秘密。危险的!

* 如果第二个 32 位为负数,则需要付出一些努力,但可以发现这一点。

于 2012-06-21T14:49:49.037 回答
2

种子毫无意义。一个好的随机生成器在选择的素数上有所不同。每个随机生成器都从一个数字开始并遍历一个“环”。这意味着,您从一个数字到另一个数字,具有旧的内部值。但过了一会儿,你又回到起点,重新开始。所以你运行循环。(随机生成器的返回值不是内部值)

如果您使用质数来创建一个环,那么在您完成所有可能的数字的完整循环之前,该环中的所有数字都会被选中。如果您采用非素数,则不会选择所有数字并且您会获得更短的周期。

在您再次返回第一个元素之前,更高的素数意味着更长的周期。所以,安全随机发生器只是有一个更长的周期,在再次到达开始之前,这就是它更安全的原因。您无法像使用较短的周期那样容易地预测数字生成。

换句话说:您必须全部替换。

于 2012-06-24T22:22:59.473 回答
0

我将尝试使用非常基本的单词,以便您可以轻松理解 Random 和 secureRandom 之间的区别以及 SecureRandom 类的重要性。

有没有想过如何生成 OTP(一次性密码)?为了生成 OTP,我们也使用 Random 和 SecureRandom 类。现在为了使您的 OTP 更强大,SecureRandom 更好,因为它需要 2^128 次尝试才能破解当前机器几乎不可能的 OTP,但如果使用 Random 类,那么您的 OTP 可能会被可能损害您数据的人破解,因为它花了只需 2^48 次尝试即可破解。

于 2018-07-11T11:06:41.250 回答