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

2022-01-10 00:00:00 random cryptography security java

我的团队收到了一些生成随机令牌的服务器端代码(Java 中),我对此有疑问 -

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

这些令牌的用途相当敏感 - 用于会话 ID、密码重置链接等.因此它们确实需要加密随机以避免有人猜测它们或暴力破解它们是可行的.令牌是长"的,所以它是 64 位长.

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.

代码当前使用 java.util.Random 类来生成这些标记.java.util 的 文档.Random 明确说明如下:

The code currently uses the java.util.Random class to generate these tokens. The documentation for java.util.Random clearly states the following:

java.util.Random 的实例不是加密安全的.考虑改为使用 SecureRandom 来获取加密安全的伪随机数生成器,以供对安全敏感的应用程序使用.

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() 方法生成令牌.

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.

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

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?

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

Currently the code seed's the Random once at startup

推荐答案

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

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

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

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:

这是一个线性同余伪随机数生成器,如由 D. H. Lehmer 定义并由 Donald E. Knuth 在计算机编程的艺术, 第 3 卷:半数值算法,第 3.2.1 节.

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 等的东西.

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.

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

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.

更好"的种子不会提高安全性.如果您使用 SecureRandom 生成的随机值作为种子,或者甚至通过掷骰子数次来产生该值,这根本无关紧要.

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.

攻击者将简单地根据观察到的输出值计算种子.在 java.util.Random 的情况下,这比 2^48 花费的时间明显少.不信的人可以试试这个实验,它表明你可以预测未来的 Random 输出,在大约 2^16 的时间内仅观察两个(!)输出值.现在,在现代计算机上预测随机数的输出甚至不需要一秒钟.

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.

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

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...

相关文章