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

我的团队已经交出了一些生成随机令牌的服务器端代码(使用Java),我也有同样的问题 –

这些令牌的目的是相当敏感的 – 用于会话ID,密码重置链接等。所以他们确实需要encryption随机,以避免有人猜测他们或蛮横迫使他们切实可行。 令牌是一个“长”,所以它是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.Randomclass。 然后它使用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在“艺术计算机程序devise”第3卷:研究mathalgorithm第3.2.1节中描述。

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

Hugo Krawczyk写了一篇关于如何预测这些LCG的很好的论文(“如何预测同余发生器”)。 如果你是幸运的并且感兴趣,你仍然可以在网上find一个免费的,可下载的版本。 还有更多的研究清楚地表明,你不应该使用LCG来达到安全关键的目的。 这也意味着你的随机数字现在可预测的,你不需要会话ID之类的东西。

如何打破线性同余发生器

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

这种安全性并没有通过“更好的”种子来改善。 如果使用SecureRandom生成的随机值进行种子处理,甚至通过多次SecureRandom生成该值,则无关紧要。

攻击者将简单地从观察到的输出值中计算出种子。 在java.util.Random的情况下,这要比2 ^ 48 less得多。 不信道的人可能会尝试这个实验 ,在那里显示你可以预测将来的Random输出只能观察到两个(!)输出值的时间大概是2 ^ 16。 现代计算机甚至不需要花一秒钟来预测你的随机数字的输出。

结论

replace你当前的代码。 专门使用SecureRandom 。 那么至less你会有一点保证,结果是难以预料的。 如果你想要一个密码安全的PRNG的属性(在你的情况下,这就是你想要的),那么你只能使用SecureRandom 。 聪明地改变它应该被使用的方式几乎总是会导致不太安全的东西…

一个随机只有48位,其中SecureRandom可以有128位。 所以重复安全随机的机会非常小。

随机使用system clock作为种子/或生成种子。 所以如果攻击者知道种子生成的时间,就可以很容易地复制它们。 但SecureRandom从你的os获取Random Data (它们可以是键击等间隔 – 大多数操作系统收集这些数据将它们存储在文件中 – /dev/random and /dev/urandom in case of linux/solaris )并将其用作种子。
因此,如果小标记大小没有问题(在随机情况下),您可以继续使用您的代码,因为您正在使用SecureRandom生成种子。 但是如果你想要更大的令牌(不能受到brute force attacks ),请使用SecureRandom –
在随机的情况下,只需要2^48尝试,使用今天的高级cpu就有可能在实际的时间内破解它。 但是为了安全随机2^128尝试将是必需的,这将需要几年和几年才能与今天的先进机器相媲美。

看到这个链接了解更多细节。
编辑
在阅读@emboss提供的链接之后,很明显的是种子,不pipe它是随机的,不应该和java.util.Random一起使用。 通过观察输出来计算种子是非常容易的。

去SecureRandom – 使用本地PRNG (如上面的链接中给出的),因为每次调用nextBytes()都会从/dev/random文件中获取随机值。 这样一来,观察输出的攻击者将无法做出任何事情,除非他正在控制/dev/random文件的内容(这是不太可能的)
sha1 prngalgorithm只计算一次种子,如果您的虚拟机使用相同的种子运行数月,则可能被被动地观察输出的攻击者破解。

注意 – 如果你调用nextBytes()速度比你的操作系统能够将随机字节(熵)写入/dev/random更快,那么使用NATIVE PRNG时可能会遇到麻烦。 在这种情况下,请使用SecureRandom的SHA1 PRNG实例,并且每隔几分钟(或某个时间间隔),使用SecureRandom的NATIVE PRNG实例的nextBytes()中的值对此实例进行种子处理。 将这两者并行运行将确保您以正确的随机值进行播种,同时不会耗尽操作系统获得的熵。

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

这两个类是相似的,我想你只需要改变RandomSecureRandom与重构工具和大部分现有的代码将工作。

如果改变现有的代码是一个负担得起的任务,我build议你使用Javadocbuild议的SecureRandom类。

即使您发现Random类实现在内部使用SecureRandom类。 你不应该认为这是理所当然的:

  1. 其他虚拟机的实现也是一样的。
  2. 在未来版本的JDK中实现Random类仍然使用SecureRandom类

因此,按照文档build议并直接使用SecureRandom是一个更好的select。

种子是没有意义的。 一个好的随机发生器在select的主数字上有所不同。 每个随机生成器从一个数字开始,并遍历一个“环”。 这意味着,你从一个数字到下一个,具有旧的内部价值。 但过了一段时间,你又重新开始,重新开始。 所以你运行周期。 (来自随机发生器的返回值不是内部值)

如果您使用素数创build戒指,则在完成所有可能数字的完整循环之前,将select该戒指中的所有数字。 如果你采用非素数,并不是所有的数字都被选中,你会得到更短的周期。

更高的素数意味着更长的周期,再次返回到第一个元素之前。 所以,安全的随机发生器只有更长的周期,再次到达开始,这就是为什么它更安全。 你不能预测数字生成就像更短的周期一样容易。

换句话说:你必须全部取代。

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位是负数,则需要付出一些努力,但是可以find。