我的团队交出了一些生成随机令牌的服务器端代码(用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()
方法获取用于实例化{的种子{1}}类。然后它使用java.util.Random
方法生成令牌。
现在我的问题 - 考虑到使用java.util.Random.nextLong()
播种java.util.Random
,它仍然不安全吗?我是否需要修改代码才能使用java.security.SecureRandom
专门生成令牌?
目前代码种子在启动时为java.security.SecureRandom
答案 0 :(得分:224)
标准的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
的情况下,这比<^>显着更少时间1>。不相信者可以尝试这个experiment,其中显示你可以预测未来的Random
输出仅观察到两个(!)输出值,大约为2 ^ 16。在现代计算机上甚至不需要一秒钟来预测随机数的输出。
替换您当前的代码。仅使用SecureRandom
。那么至少你会有一点保证结果很难预测。如果您想要加密安全PRNG的属性(在您的情况下,这就是您想要的),那么您只需要使用SecureRandom
。聪明地改变它应该使用的方式几乎总会导致一些不太安全的东西......
答案 1 :(得分:70)
随机只有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
的尝试将是必需的,这将需要数年和数年来与当今先进的机器一起收支平衡。
有关详细信息,请参阅 this 链接。
的修改
在阅读了@emboss提供的链接之后,很明显种子,无论它是随机的,
不应该与java.util.Random一起使用。通过观察输出来计算种子非常容易。
转到SecureRandom - 使用原生PRNG (如上面链接中所示),因为每次调用/dev/random
时,它会从nextBytes()
文件中获取随机值}。这样,观察输出的攻击者除非控制/dev/random
文件的内容(这是非常不可能的),否则将无法做出任何事情。
sha1 prng 算法仅计算一次种子,如果您的VM使用相同的种子运行数月,则可能会被被动观察输出的攻击者破解。
注意 - 如果您调用nextBytes()
的速度超过您的操作系统能够将随机字节(熵)写入/dev/random
,则在使用时可能会遇到麻烦NATIVE PRNG 。在这种情况下,使用SecureRandom的SHA1 PRNG实例并且每隔几分钟(或一些间隔),使用来自SecureRandom的NATIVE PRNG实例的nextBytes()
的值来植入此实例。平行运行这两个将确保您定期播种真正的随机值,同时也不会耗尽操作系统获得的熵。
答案 2 :(得分:9)
如果使用相同的种子运行两次java.util.Random.nextLong()
,它将生成相同的数字。出于安全原因,您希望坚持java.security.SecureRandom
,因为它的可预测性要低得多。
2个类是相似的,我认为您只需要使用重构工具将Random
更改为SecureRandom
,并且您的大部分现有代码都可以使用。
答案 3 :(得分:3)
如果更改现有代码是一项经济实惠的任务,我建议您使用Javadoc中建议的SecureRandom类。
即使您发现Random类实现在内部使用SecureRandom类。你不应该理所当然地认为:
因此,更好的选择是遵循文档建议并直接使用SecureRandom。
答案 4 :(得分:2)
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的秘密,其程度可以用很少的efford计算整个秘密。危险!强>
*如果第二个32位是负数,则需要付出一些努力,但是可以找到它。
答案 5 :(得分:2)
种子毫无意义。一个好的随机生成器在所选择的prime数中不同。每个随机生成器都从一个数字开始,并通过“环”迭代。这意味着,您使用旧的内部值从一个数字到下一个数字。但过了一段时间,你再次到达起点并重新开始。所以你运行周期。 (随机生成器的返回值不是内部值)
如果使用素数创建一个铃声,则在完成所有可能数字的完整循环之前,将选择该环中的所有数字。如果您使用非素数,则不会选择所有数字,并且您的周期会缩短。
在您再次返回第一个元素之前,更高的素数意味着更长的周期。因此,安全随机生成器只有更长的周期,再次到达开头之前,这就是为什么它更安全。您无法使用更短的周期来预测数字生成。
换句话说:你必须全部替换。
答案 6 :(得分:0)
我将尝试使用非常基本的单词,以便您可以轻松理解Random和secureRandom之间的区别以及SecureRandom类的重要性。
您是否想知道如何生成一次性密码(一次性密码)? 为了生成OTP,我们也使用Random和SecureRandom类。现在,要使您的OTP变得更强大,SecureRandom会更好,因为它尝试了2 ^ 128次才能破解OTP,这在当前机器上几乎是不可能的,但是如果使用Random Class,那么您的OTP可能会被可能破坏您数据的人破解只需2 ^ 48尝试,即可破解。