java.util.Random和java.security.SecureRandom之间的区别
我的团队已经交出了一些生成随机令牌的服务器端代码(使用Java),并且我有一个关于相同的问题 -
这些令牌的用途相当敏感 - 用于会话ID,密码重置链接等。因此,它们确实需要密码随机,以避免有人猜测它们或蛮横逼迫它们。 令牌是一个“长”,所以它是64位长。
该代码当前使用java.util.Random
类来生成这些令牌。 java.util.Random
的文档([http://docs.oracle.com/javase/7/docs/api/java/util/Random.html][1])明确指出以下内容:
java.util.Random的实例不具有密码安全性。 请考虑使用SecureRandom来获取密码安全的伪随机数生成器,供安全敏感的应用程序使用。
然而,代码当前使用java.util.Random
是这样的 - 它实例化java.security.SecureRandom
类,然后使用SecureRandom.nextLong()
方法获取用于实例化java.util.Random
的种子java.util.Random
班。 然后它使用java.util.Random.nextLong()
方法来生成令牌。
所以现在我的问题 - 是否仍然不安全,因为java.util.Random
使用java.security.SecureRandom
进行播种? 我是否需要修改代码以使其仅使用java.security.SecureRandom
生成令牌?
目前代码种子在启动时是Random
标准的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
生成的随机值进行种子处理,甚至通过多次SecureRandom
骰来生成该值,则无关紧要。
攻击者将简单地根据观察到的输出值计算种子。 这在java.util.Random
的情况下花费的时间比2 ^ 48少得多。 不信道的人可能会尝试这个实验,在那里显示你可以预测将来的Random
输出在时间上仅观察到两个(!)输出值大约2 ^ 16。 现代计算机甚至不需要一秒就可以预测你的随机数的输出。
结论
替换您的当前代码。 专门使用SecureRandom
。 那么至少你会有一点担保,结果很难预测。 如果你想要一个加密安全PRNG的属性(在你的情况下,这就是你想要的),那么你只能使用SecureRandom
。 聪明地改变它应该被使用的方式几乎总是会导致不太安全的东西......
一个随机只有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
尝试将是必需的,这需要几年和几年才能与今天的先进机器相媲美。
请参阅此链接了解更多详情。
编辑
阅读@emboss提供的链接后,很明显,种子,不管它是随机的,都不应该与java.util.Random一起使用。 通过观察输出来计算种子是非常容易的。
使用SecureRandom - 使用本地PRNG (如上面的链接所示),因为每次调用nextBytes()
都会从/dev/random
文件中获取随机值。 这样一来,观察输出的攻击者将无法做出任何事情,除非他控制/dev/random
文件的内容(这是不太可能的)
sha1 prng算法只计算一次种子,如果您的虚拟机使用相同种子运行数月,则可能被被动观察输出的攻击者破解。
注 - 如果你调用nextBytes()
速度比你的操作系统能够将随机字节(熵)写入/dev/random
更快,那么使用NATIVE PRNG时可能会遇到麻烦。 在这种情况下,使用SecureRandom的SHA1 PRNG实例并且每隔几分钟(或某个时间间隔),使用SecureRandom的NATIVE PRNG实例的nextBytes()
中的值为此实例创建种子。 将这两者并行运行将确保您定期播种真正的随机值,同时不会耗尽操作系统获得的熵。
如果使用相同的种子运行两次java.util.Random.nextLong()
,它将生成相同的数字。 出于安全考虑,您希望坚持使用java.security.SecureRandom
因为它的可预测性较差。
这两个类是相似的,我想你只需要用重构工具将Random
更改为SecureRandom
,并且大部分现有代码都可以工作。
上一篇: Difference between java.util.Random and java.security.SecureRandom