java.util.random和java.security.securelrandom之间的区别

Difference between java.util.Random and java.security.SecureRandom

我的团队得到了一些产生随机令牌的服务器端代码(Java),我有一个相同的问题。

这些令牌的用途相当敏感-用于会话ID、密码重置链接等,因此它们需要是加密随机的,以避免有人猜测它们,或者可能地强行执行它们。令牌是"长"的,所以它是64位长。

代码当前使用java.util.Random类来生成这些令牌。文档([ http://dos.Oracle .COM/JavaSe/ 7 /DOCS/API/Java/UTL/RAPID.HTML](1))对EDCOX1 0表示清楚如下:

Instances of java.util.Random are not cryptographically secure. Consider instead using SecureRandom to get a cryptographically secure pseudo-random number generator for use by security-sensitive applications.

但是,代码当前使用java.util.Random的方式是:它实例化java.security.SecureRandom类,然后使用SecureRandom.nextLong()方法获取用于实例化java.util.Random类的种子。然后使用java.util.Random.nextLong()方法生成令牌。

所以我现在的问题是,考虑到使用java.security.SecureRandom播种java.util.Random,是否仍然不安全?我是否需要修改代码,以便它专门使用java.security.SecureRandom来生成令牌?

目前,代码种子在启动时是一次Random


标准的OracleJDK7实现使用所谓的线性同余生成器在java.util.Random中生成随机值。

摘自java.util.Random源代码(jdk 7u2),摘自对生成随机值的方法protected int next(int bits)的注释:

This is a linear congruential pseudorandom number generator, as
defined by D. H. Lehmer and described by Donald E. Knuth in
The Art of Computer Programming, Volume 3:
Seminumerical Algorithms, section 3.2.1.

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

Hugo Krawczyk写了一篇很好的论文,介绍了如何预测这些LCG("如何预测同余生成器")。如果你很幸运也很感兴趣,你仍然可以在网上找到一个免费的,可下载的版本。还有更多的研究清楚地表明,您不应该将LCG用于安全关键的目的。这也意味着您的随机数现在是可预测的,这是您不希望用于会话ID等的。

如何打破线性同余发生器

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

"更好"的种子并不能提高安全性。如果您使用SecureRandom生成的随机值进行种子植入,或者甚至通过多次滚动模具来生成该值,这根本不重要。

攻击者只需根据观察到的输出值计算种子。对于java.util.Random来说,这比2^48花费的时间要短得多。怀疑者可能会尝试这个实验,结果表明你可以预测未来的Random输出,只观察到两个(!)输出值在时间上约为2^16。在现代计算机上,现在预测随机数的输出甚至不需要一秒钟。

结论

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


一个随机数只有48位,其中SecureRandom最多可以有128位。所以在SecureRandom中重复的机会很小。随机使用system clock作为种子/或生成种子。因此,如果攻击者知道种子的生成时间,就可以很容易地复制它们。但是SecureRandom从你的os中获取Random Data(它们可以是击键之间的间隔时间等—大多数操作系统收集这些数据存储在文件中—/dev/random and /dev/urandom in case of linux/solaris并将其用作种子。因此,如果小令牌大小正常(在随机情况下),您可以继续使用代码而不做任何更改,因为您使用SecureRandom来生成种子。但是,如果您想要更大的令牌(不受brute force attacks的约束),请使用secureRandom——如果是随机的,则只需要2^48次尝试,使用今天的高级CPU,可以在实际时间内将其中断。但是,对于SecureRandom2^128系统,将需要进行多次尝试,这将需要多年时间才能与当今先进的机器抗衡。有关详细信息,请参阅此链接。编辑在阅读了@emboss提供的链接后,很明显种子,不管它是随机的,不应与java.util.random一起使用。通过观察产量很容易计算出种子。转到secureRandom-使用本机prng(如上面链接中所示),因为每次调用nextBytes()时,它都从/dev/random文件中获取随机值。这样,除非攻击者控制/dev/random文件的内容(这是非常不可能的),否则观察输出的攻击者将无法识别任何内容。sha1prng算法只计算一次seed,如果您的虚拟机使用相同的seed运行数月,则可能会被被动观察输出的攻击者破解。注意-如果调用nextBytes()的速度比操作系统向/dev/random中写入随机字节(熵)的速度快,那么在使用本机prng时可能会遇到麻烦。在这种情况下,使用SecureRandom的sha1 prng实例,每隔几分钟(或某些间隔),将该实例的值从SecureRandom的本机prng实例的nextBytes()中设定。运行这两个并行操作将确保定期使用真正的随机值播种,同时也不会耗尽操作系统获得的熵。


如果用相同的种子运行两次java.util.Random.nextLong(),它将产生相同的数目。出于安全考虑,您希望继续使用java.security.SecureRandom,因为它的可预测性要差得多。

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


如果更改现有代码是一项负担得起的任务,我建议您使用JavaDoc中建议的SecureRandom类。

即使您发现随机类实现在内部使用SecureRandom类。你不应该想当然地认为:

  • 其他虚拟机实现也做同样的事情。
  • 在将来的JDK版本中,随机类的实现仍然使用SecureRandom类
  • 因此,最好遵循文档建议并直接使用SecureRandom。


    种子毫无意义。一个好的随机发生器在所选素数上是不同的。每个随机生成器都从一个数字开始,并通过一个"环"迭代。也就是说,从一个数字到下一个数字,使用旧的内部值。但过了一会儿,你又回到起点,重新开始。所以你运行循环。(随机生成器的返回值不是内部值)

    如果您使用质数创建一个环,那么在您完成一个完整的循环之前,该环中的所有数字都会被选中。如果你采用非质数,并不是所有的数字都会被选择,你会得到更短的周期。

    更高的素数意味着,在您再次返回第一个元素之前,周期更长。因此,安全随机生成器只有较长的周期,在再次到达开始之前,这就是它更安全的原因。你不能像用更短的周期那样容易预测数字的产生。

    换言之:你必须全部替换。


    当前java.util.Random.nextLong()的引用实现对方法next(int)进行了两次调用,该方法直接公开当前种子的32位:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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位为负,则需要做一些努力,但可以发现这一点。


    我将尝试使用非常基本的单词,这样您就可以很容易地理解Random和SecureRandom之间的区别以及SecureRandom类的重要性。

    有没有想过OTP(一次性密码)是如何生成的?为了生成一个OTP,我们也使用了Random和SecureRandom类。现在,为了使您的OTP更强大,SecureRandom更好,因为它花了2^128次尝试,破解了目前机器几乎不可能破解的OTP,但是如果使用了随机类,那么您的OTP可以被一些人破解,因为它只花了2^48次尝试就破解了。