且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

java.util.Random和java.security.SecureRandom之间的差异

更新时间:2023-01-11 21:35:34

JDK 7实现使用所谓的线性并行生成器在 java.util.Random 中生成随机值。



取自 java.util.Random 源代码(JDK 7u2),从注释方法 protected int next(int bits),它是生成随机值的那个:


这是线性同余伪随机数生成器,如DH Lehmer定义的并且由Donald E.Knuth在
中描述的计算机程序设计的艺术,第3卷:




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



Hugo Krawczyk写了一篇关于如何预测这些LCG的好文章(如何预测同余发电机)。如果你幸运和有兴趣,你仍然可以在网上找到一个免费的,可下载的版本。还有更多的研究清楚地表明,你应该从不使用LCG的安全关键目的。这也意味着您的随机数 现在是可预测的,您不想要的会话ID等。



如何打破线性同余生成器



假设攻击者必须等待LCG在完整周期后重复。即使具有***循环(其循环关系中的模数m),也很容易在比整个循环少得多的时间内预测未来值。毕竟,它只是一堆需要解决的模块方程,一旦你观察到LCG的足够的输出值,这很容易。



使用更好的种子,安全性不会提高。如果你使用由 SecureRandom 生成的随机值进行种子,或者甚至通过滚动一个模具几次来生成该值,就没关系。



攻击者只需从观察到的输出值计算种子。在 java.util.Random 的情况下,这要花费显着少于2 ^ 48的时间。不相信者可以尝试此实验,其中显示您可以预测未来 Random 输出只观察到两个(!)输出值在时间上大约为2 ^ 16。



结论



p>替换当前代码。仅使用 SecureRandom 。然后,至少你会有一点保证,结果将很难预测。如果你想要的密码安全的PRNG的属性(在你的情况下,这是你想要的),那么你必须用 SecureRandom 。聪明地改变它应该被使用的方式将几乎总是导致不太安全...

My team got handed over some server side code (in Java) that generates random tokens and I have a question regarding the same -

The purpose of these tokens is fairly sensitive - used for session id, password reset links etc. So they do need to be cryptographically random to avoid somebody guessing them or brute force them feasibly. The token is a "long" so it is 64 bits long.

The code currently uses the java.util.Random class to generate these tokens. The documentation ([http://docs.oracle.com/javase/7/docs/api/java/util/Random.html][1]) for java.util.Random clearly states the following:

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.

However, the way the code is currently using java.util.Random is this - It instantiates the java.security.SecureRandom class and then uses the SecureRandom.nextLong() method to obtain the seed that is used for instantiating the java.util.Randomclass. Then it uses java.util.Random.nextLong() method to generate the token.

So my question now - Is it still insecure given that the java.util.Random is being seeded using java.security.SecureRandom? Do I need to modify the code so that it uses java.security.SecureRandom exclusively to generate the tokens?

Currently the code seed's the Random once at startup

The standard Oracle JDK 7 implementation uses what's called a Linear Congruential Generator to produce random values in java.util.Random.

Taken from java.util.Random source code (JDK 7u2), from a comment on the method protected int next(int bits), which is the one that generates the random values:

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.

Predictability of Linear Congruential Generators

Hugo Krawczyk wrote a pretty good paper about how these LCGs can be predicted ("How to predict congruential generators"). If you're lucky and interested, you may still find a free, downloadable version of it on the web. And there's plenty more research that clearly shows that you should never use an LCG for security-critical purposes. This also means that your random numbers are predictable right now, something you don't want for session IDs and the like.

How to break a Linear Congruential Generator

The assumption that an attacker would have to wait for the LCG to repeat after a full cycle is wrong. Even with an optimal cycle (the modulus m in its recurrence relation) it is very easy to predict future values in much less time than a full cycle. After all, it's just a bunch of modular equations that need to be solved, which becomes easy as soon as you have observed enough output values of the LCG.

The security doesn't improve with a "better" seed. It simply doesn't matter if you seed with a random value generated by SecureRandom or even produce the value by rolling a die several times.

An attacker will simply compute the seed from the output values observed. This takes significantly less time than 2^48 in the case of java.util.Random. Disbelievers may try out this experiment, where it is shown that you can predict future Random outputs observing only two(!) output values in time roughly 2^16. It takes not even a second on a modern computer to predict the output of your random numbers right now.

Conclusion

Replace your current code. Use SecureRandom exclusively. Then at least you will have a little guarantee that the result will be hard to predict. If you want the properties of a cryptographically secure PRNG (in your case, that's what you want), then you have to go with SecureRandom only. Being clever about changing the way it was supposed to be used will almost always result in something less secure...