java.util.Random 和 java.security.SecureRandom 之间的区别

Posted

技术标签:

【中文标题】java.util.Random 和 java.security.SecureRandom 之间的区别【英文标题】:Difference between java.util.Random and java.security.SecureRandom 【发布时间】:2012-06-18 13:19:13 【问题描述】:

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

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

代码当前使用java.util.Random 类来生成这些令牌。 java.util.Random 的 documentation 明确指出以下内容:

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

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

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

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

【问题讨论】:

播种后,java.util.Random 的输出是确定的数字序列。你可能不想要那个。 代码是在启动时为Random 播种一次,还是为每个令牌播种一个新的?希望这是一个愚蠢的问题,但我想我会检查一下。 Random 只有一个 48 位的内部状态,并且会在 2^48 次调用 nextLong() 后重复,这意味着它不会产生所有可能的 longdouble 值。跨度> 还有一个严重的问题。 64 位意味着 1.84*10^19 种可能的组合,这太少而无法承受复杂的攻击。有些机器可以在 60 小时内以每秒 90*10^9 个密钥破解 56 位 DES 代码(减少 256 倍)。使用 128 位或两个长整数! 【参考方案1】:

我会尽量使用非常基础的词汇,让你更容易理解 Random 和 secureRandom 的区别以及 SecureRandom 类的重要性。

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

【讨论】:

【参考方案2】:

随机只有 48 位,而 SecureRandom 最多可以有 128 位。因此,在安全随机中重复的机会非常小。随机使用system clock 作为种子/或生成种子。因此,如果攻击者知道生成种子的时间,它们可以很容易地被复制。但是 SecureRandom 从您的 os 中获取 Random Data(它们可以是击键之间的间隔等 - 大多数操作系统收集这些数据并将它们存储在文件中 - /dev/random and /dev/urandom in case of linux/solaris)并将其用作种子。 因此,如果小令牌大小没问题(在 Random 的情况下),您可以继续使用您的代码而无需任何更改,因为您使用 SecureRandom 来生成种子。但是,如果您想要更大的令牌(不受brute force attacks 的约束),请使用 SecureRandom - 如果是随机的,则只需要2^48 尝试,使用当今先进的 cpu 可以在实际时间内打破它。但是对于安全随机 2^128 将需要尝试,这将需要数年时间才能在当今先进的机器上实现收支平衡。 有关详细信息,请参阅 this 链接。 编辑 阅读@emboss提供的链接后,很明显种子,无论它可能是随机的, 不应与 java.util.Random 一起使用。通过观察输出来计算种子非常容易。选择 SecureRandom - 使用 Native PRNG(如上面的链接中给出的),因为它每次调用nextBytes()/dev/random 文件中获取随机值。这样,除非他控制/dev/random 文件的内容,否则观察输出的攻击者将无法识别任何内容(这不太可能)sha1 prng 算法只计算一次种子,如果您的虚拟机使用同一个种子运行了几个月,它可能会被被动观察输出的攻击者破解。注意 - 如果您调用nextBytes() 的速度比您的操作系统能够将随机字节(熵)写入/dev/random 的速度快,那么在使用NATIVE PRNG时可能会遇到麻烦>。在这种情况下,使用 SecureRandom 的 SHA1 PRNG 实例,每隔几分钟(或某个时间间隔),使用来自 SecureRandom 的 NATIVE PRNG 实例的nextBytes() 的值播种此实例。并行运行这两个将确保您使用真正的随机值定期播种,同时也不会耗尽操作系统获得的熵。

【讨论】:

预测Random所需的时间远少于2^48,OP根本不应该使用Random @emboss :我说的是暴力破解。 小心 Linux:它会达到熵耗尽(在 VM 中比在硬件中更多)!查看/proc/sys/kernel/random/entropy_avail 并检查一些线程转储,在/dev/random 上阅读时等待时间不会太长 请注意,Oracle JRE(至少 1.7)默认使用 /dev/urandom 而不是 /dev/random,因此您的答案后缀不再正确。验证检查 $JAVA_HOME/lib/security/java.security 的 securerandom.source 属性 我们的 java.security 文件有 securerandom.source=file:/dev/urandom 而不是 file:///dev/urandom(冒号后面的两个斜杠表示文件协议,然后一个斜杠表示根文件系统),导致它回退到 /dev/random,从而导致熵池耗尽的问题。无法对其进行编辑,因此必须在应用启动时将系统属性 java.security.egd 设置为正确的。【参考方案3】:

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

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

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

因此,最好遵循文档建议并直接使用 SecureRandom。

【讨论】:

我不相信最初的问题表明java.util.Random 实现在内部使用SecureRandom,它说他们的代码 使用SecureRandom 播种Random .不过,到目前为止,我都同意这两个答案;最好使用SecureRandom 来避免明确的确定性解决方案。【参考方案4】:

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

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

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

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

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

如何破坏线性同余生成器

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

“更好”的种子不会提高安全性。如果您使用SecureRandom 生成的随机值作为种子,或者甚至通过掷骰子数次来产生该值,这都无关紧要。

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

结论

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

【讨论】:

非常有帮助,也许您还可以解释 SecureRandom 的工作原理(就像您解释 Random 的工作原理一样).. 这违背了secureRandom的目的 我知道,这是一个艰难的教训。但是一个艰难的密码和难以找到的来源运作良好。 Notch 可以从中学到一些东西(他将他的用户密码编码在一个 .lastlogin 文件中,使用“passwordfile”作为密钥进行基本加密编码) 真正的问题是:如果 java 可以使用类似的 API 生成更安全的 prng,为什么不直接替换损坏的? @JoelCoehoorn 并不是说​​Random 坏了——它应该只是在不同的场景中使用。当然,您始终可以使用 SecureRandom。但总的来说,SecureRandom 明显比纯Random 慢。在某些情况下,您只对良好的统计属性和出色的性能感兴趣,但您并不真正关心安全性:蒙特卡洛模拟就是一个很好的例子。我在similar answer 中对此进行了 cmets,也许你会发现它很有用。【参考方案5】:

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

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

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

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

【讨论】:

【参考方案6】:

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

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);

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

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

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

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

【讨论】:

正确。在jazzy.id.au/default/2010/09/20/…查看如何快速破解java.util.random!【参考方案7】:

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

这两个类是相似的,我认为您只需使用重构工具将Random 更改为SecureRandom,您现有的大部分代码都可以使用。

【讨论】:

如果您获取任何 PRNG 的两个实例并使用相同的值作为种子,您总是会得到相同的随机数,即使使用 SecureRandom 也不会改变这一点。所有 PRNG 都是确定性的,因此如果您知道种子,则可以预测。 有不同的 SecureRandom 实现,有些是 PRNG,有些不是。另一方面,java.util.Random 始终是 PRNG(在其 Javadoc 中定义)。 在 Linux 上,如果您使用 new SecureRandom() 并因此使用 /dev/urandom,您将不断从操作系统设备中获取额外的熵。严格来说,它不是伪随机数生成器 (PRNG)。可以这么说,它是一个混合的 PRNG 增强熵(并且是一个很好的工程)。

以上是关于java.util.Random 和 java.security.SecureRandom 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

java.lang.IllegalMonitorStateException: 在 java.util.Random.nextGaussian(Random.java:187) 解锁无主监视器

import java.util.Random; 构造函数来取随机数

import java.util.Random; 构造函数来取随机数

源码分析:java.util.Random

Java – Generate random integers in a range

Random类常用的方法和使用