我的团队得到了移交生成随机令牌一些服务器端代码(在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
一次启动
标准的Oracle JDK 7的实现使用一种名为线性同余发生器产生随机值java.util.Random
。
来自java.util.Random
的源代码(JDK 7U2),从上所述方法的评论protected int next(int bits)
,这是产生的随机值的一个:
这是一个线性同余伪随机数发生器,由DH莱默所定义并且由Donald E. Knuth的在第3卷中说明: ,第3.2.1节。
线性同余发生器的可预测性
雨果Krawczyk写了一个相当不错的一篇关于这些的LCG如何预测(“如何预测同余生成”)。 如果你够幸运和兴趣,你仍可能会发现它在网络上下载的免费版本。 并且有大量更多的研究清楚地表明,你不应该使用安全关键目的的LCG。 这也意味着,你的随机数是可以预见的,现在,你不想会话ID和喜欢的东西。
如何打破线性同余发生器
攻击者将不得不等待LCG一个完整的周期后重复的假设是错误的。 甚至具有最佳周期(模数m在其递推关系)这是很容易在少得多的时间来预测未来的值比一个完整的循环。 毕竟,它只是一堆需要解决,这很容易,只要你所观察到的LCG的足够的输出值变为模块化方程。
安全不是以“更好的”种子提高。 它只是如果你与生成的随机值种子无关紧要SecureRandom
甚至通过掷骰子几次产生的价值。
攻击者将简单地计算从所观察到的输出值的种子。 这需要超过2 ^ 48时显著在少的情况下java.util.Random
。 不信道可以尝试这个实验 ,它表明你可以预测未来的Random
输出在时间大约2 ^ 16观察只有两个(!)的输出值。 这需要甚至没有第二个现代化的电脑上,现在来预测你的随机数的输出。
结论
更换你当前的代码。 使用SecureRandom
完全。 那么,你至少会有一点点保证结果将难以预料。 如果你想以加密安全PRNG的属性(在你的情况,这就是你想要的),那么你必须去SecureRandom
只。 作为聪明的有关更改它应该被用来几乎总是导致更不安全的东西的方式......
随机仅具有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的,可以打破它在实际时间。 但对于SecureRandom的2^128
的尝试将是必需的,这将需要数年时间和多年与当今先进的机器甚至破裂。
请参阅此链接了解更多详情。
编辑
阅读由@emboss提供的链接后,很明显的是种子,但随机它也许不应该用了java.util.Random使用。 这是很容易通过观察输出来计算种子。
去的SecureRandom -使用本地PRNG(如上面的链接给出),因为它需要随机值从/dev/random
文件每次调用nextBytes()
这样,攻击者观察输出将不能够做出来的任何东西,除非他是控制内容/dev/random
文件(这是不太可能)
该SHA1 PRNG算法计算的种子只有一次,如果你的虚拟机使用相同的种子运行了几个月,它可能被攻击者是谁被动地观察输出被破解。
注意 -如果您呼叫的nextBytes()
比你更快的操作系统能够随机字节(熵)写入到/dev/random
,使用本地PRNG时可能降落陷入困境。 在这种情况下使用的SecureRandom和每隔几分钟(或一些间隔)的SHA1 PRNG实例,种子与来自值这种情况下nextBytes()
的SecureRandom的NATIVE PRNG实例。 运行这两个平行将确保你经常与真正的随机数种子,同时也不会耗尽由操作系统获得的熵。
如果您运行两次java.util.Random.nextLong()
使用相同的种子,它会产生相同的号码。 出于安全考虑,您希望与坚持java.security.SecureRandom
,因为它是少了很多可以预见的。
在2类是相似的,我想你只需要改变Random
到SecureRandom
与重构工具和您现有的大多数代码将工作。
如果更改现有的代码是一种经济实惠的任务,我建议你使用SecureRandom类中的Javadoc建议。
即使你发现Random类实现使用SecureRandom类内部。 你不应该想当然地认为:
- 其他虚拟机的实现做同样的事情。
- 在JDK的未来版本Random类的实现仍然使用SecureRandom类
所以这是一个更好的选择,按照文档的建议,并直接与SecureRandom的去。
的当前参考实现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);
}
的结果的高32位nextLong()
是在时间的种子的比特。 由于种子的宽度为48位(说的Javadoc),只须*遍历剩余的16个比特(这是仅65.536尝试),以确定哪些所产生的第二个32位种子。
一旦种子是已知的,所有下列标记可以很容易地被计算出来。
使用的输出nextLong()
直接,巴布亚新几内亚的部分秘密的程度,完整的秘密可以用很少的efford来计算。 危险!
*有如果第二个32位的是负数需要一些努力,但我们可以发现这一点。
种子是没有意义的。 一个良好的随机发生器的不同之处所选primenumber。 每一个随机数发生器通过“环”从数量和迭代开始。 这意味着,你从一个数了下,与老内在价值。 但是,一段时间后,你达到再次开始,从头再来。 所以,你运行周期。 (从一个随机数发生器的返回值不是内部值)
如果你使用一个素数,用于创建一个环,在环中所有号码被选择,你通过所有可能的数字完成一个完整的循环之前。 如果你采取非质数,不是所有的数字选择,你会得到更短的周期。
高素数的意思是,一个较长的周期,你返回的第一个元素再次之前。 因此,安全的随机数生成器只是有一个较长的周期,达到再次开始,这就是为什么它是更安全了。 你无法预知的数产生一样容易与更短的周期。
与其他词:你必须全部更换。
我将尝试使用非常基本的单词,这样就可以很容易地理解随机和SecureRandom的和SecureRandom的类重要的区别。
有没有想过,OTP(一次性密码)是如何产生的? 要生成OTP我们也采用随机和的SecureRandom类。 现在,让你的OTP强,SecureRandom的是更好,因为它花了2 ^ 128试,破解OTP这几乎是不可能的。目前该机但如果使用随机类,那么你的OTP可以通过某人,因为它花了谁可能会损害您的数据被破解只是2 ^ 48尝试,开裂。