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

use*_*973 200 java security random cryptography

我的团队交出了一些生成随机令牌的服务器端代码(用Java),我对此有一个问题 -

这些令牌的目的是相当敏感的 - 用于会话ID,密码重置链接等.所以他们确实需要加密随机,以避免有人猜测它们或蛮力强制它们.令牌是"长",所以它是64位长.

代码当前使用java.util.Random该类来生成这些令牌.文档([ http://docs.oracle.com/javase/7/docs/api/java/util/Random.html] [1 ])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启动时的一次

emb*_*oss 229

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

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

这是一个线性同余伪随机数发生器,由DH Lehmer定义并由Donald E. Knuth在 The Computer of Computer Programming, Volume 3: Seminumerical Algorithms,section 3.2.1中描述.

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

Hugo Krawczyk写了一篇关于如何预测这些LCG的非常好的论文("如何预测同余发生器").如果您很幸运并感兴趣,您仍然可以在网上找到免费的可下载版本.并且有大量更多的研究清楚地表明,你应该永远使用安全关键目的的LCG.这也意味着您的随机数现在可预测的,您不需要会话ID等.

如何打破线性同余生成器

假设攻击者必须等待LCG在完整循环后重复才是错误的.即使具有最佳循环(其递归关系中的模量m),也可以在比完整循环少得多的时间内预测未来值.毕竟,它只是需要解决的一堆模块化方程式,一旦您观察到足够的LCG输出值,就会变得容易.

通过"更好"的种子,安全性不会提高.如果您使用随机生成的随机值播种,SecureRandom或者甚至通过多次掷骰子来生成该值,则无关紧要.

攻击者只需根据观察到的输出值计算种子.在这种情况下,这比2 ^ 48 花费的时间得多java.util.Random.不相信者可能会尝试这个实验,在这个实验中,你可以预测未来的Random输出只能及时观察两个(!)输出值大约2 ^ 16.在现代计算机上甚至不需要一秒钟来预测随机数的输出.

结论

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

  • 通过默默无闻的@azulflame安全很少有效 (25认同)
  • @JoelCoehoorn并不是说"随机"被打破了 - 它应该只用在不同的场景中.当然,您可以随时使用SecureRandom.但总的来说,`SecureRandom`明显慢于纯`Random`.在某些情况下,您只对良好的统计特性和出色的性能感兴趣,但您并不真正关心安全性:蒙特卡罗模拟就是一个很好的例子.我在[类似的答案](http://stackoverflow.com/questions/11099241/behaviour-of-securerandom/11101379#11101379)中对此发表了评论,也许你会发现它很有用. (11认同)
  • 非常有帮助,也许你也可以解释一下SecureRandom是如何工作的(就像你解释随机工作原理一样).. (4认同)
  • 这违背了secureRandom的目的 (4认同)
  • 真正的问题是:如果 java 可以使用类似的 API 生成更安全的 prng,为什么他们不直接替换损坏的 prng? (2认同)

Ash*_*win 72

随机只有48位,而SecureRandom最多可以有128位.因此,在securerandom中重复的可能性非常小.

随机使用system clock作为种子/或生成种子.因此,如果攻击者知道种子生成的时间,则可以轻松复制它们.但是SecureRandom的需要Random Data从你的os(按键等,他们之间可以间隔-大多数操作系统收集这些数据并将其存储在文件- /dev/random and /dev/urandom in case of linux/solaris),并将其用作种子.
因此,如果小令牌大小没问题(在随机的情况下),您可以继续使用您的代码而无需任何更改,因为您使用SecureRandom来生成种子.但是,如果你想要更大的标记(不能受到brute force attacks)去的SecureRandom -
在随机只的情况下,2^48尝试是必须的,与今天先进的CPU的,可以打破它在实际时间.但是对于安全随机的2^128尝试将是必需的,这将需要数年和数年来与当今的先进机器打破平衡.

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

转到SecureRandom - 使用本机PRNG(如上面的链接中所示),因为它/dev/random为每次调用从文件中获取随机值nextBytes().这样,攻击者观察输出将不能够做出来的任何东西,除非他控制的内容/dev/random文件(这是非常不可能的)
SHA1 PRNG算法计算的种子只有一次,如果你的虚拟机正在运行几个月使用相同种子,它可能会被一个被动地观察输出的攻击者破解.

注意 - 如果您调用的nextBytes()速度比您的操作系统能够将随机字节(熵)写入更快/dev/random,则在使用NATIVE PRNG时可能会遇到麻烦 .在这种情况下,使用SecureRandom的SHA1 PRNG实例并且每隔几分钟(或一些间隔),使用来自nextBytes()SecureRandom的NATIVE PRNG实例的值来设置此实例.平行运行这两个将确保您定期播种真正的随机值,同时也不会耗尽操作系统获得的熵.

  • 请注意,Oracle JRE(至少1.7)默认使用/ dev/urandom而不是/ dev/random,因此您的答案的后缀不再正确.验证securerandom.source属性的$ JAVA_HOME/lib/security/java.security检查 (2认同)
  • 我们的 java.security 文件有 securerandom.source=file:/dev/urandom 而不是 file:///dev/urandom (冒号后面的两个斜杠表示文件协议,然后再一个斜杠表示文件系统的根目录),导致它回退到/dev/random,这导致了熵池耗尽的问题。无法编辑它,因此必须在应用程序启动时将系统属性 java.security.egd 设置为正确的属性。 (2认同)

Mua*_*lig 9

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

这两个类是相似的,我认为您只需要使用重构工具进行更改Random,SecureRandom并且大多数现有代码都可以使用.

  • 如果您使用任何PRNG的两个实例并使用相同的值对其进行播种,则始终会获得相同的随机数,即使使用SecureRandom也不会更改它.所有PRNG都是确定性的,因此如果你知道种子就可以预测. (11认同)