我的团队交出了一些服务器端代码(在Java中),生成随机令牌,我有一个关于相同的问题

这些令牌的用途相当敏感——用于会话id、密码重置链接等。所以它们确实需要加密随机,以避免有人猜测它们或强行使用它们。令牌是一个“长”,所以它是64位长。

代码目前使用java.util.Random类来生成这些令牌。java.util.Random的文档清楚地说明了以下内容:

java.util.Random的实例不是加密安全的。相反,可以考虑使用securerrandom来获得一个加密安全的伪随机数生成器,以供对安全敏感的应用程序使用。

然而,代码目前使用java.util.Random的方式是这样的——它实例化java.security. securerrandom类,然后使用securerrandom . nextlong()方法来获得用于实例化java.util.Randomclass的种子。然后它使用java.util.Random.nextLong()方法生成令牌。

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

目前,代码种子在启动时是随机的


当前回答

A random has only 48 bits where as SecureRandom can have upto 128 bits. So the chances of repeating in securerandom is very small. Random uses the system clock as the seed/or to generate the seed. So they can be reproduced easily if the attacker knows the time at which the seed was generated. But SecureRandom takes Random Data from your os(they can be interval between keystrokes etc - most os collect these data store them in files - /dev/random and /dev/urandom in case of linux/solaris) and uses that as the seed. So if the small token size is okay(in case of Random), you can continue using your code without any changes, since you are using SecureRandom to generate the seed. But if you want larger tokens(which cannot be subject to brute force attacks) go with SecureRandom - In case of random just 2^48 attempts are required, with todays advanced cpu's it is possible to break it in practical time. But for securerandom 2^128 attempts will be required, which will take years and years to break even with today's advanced machines. See this link for more details. EDIT After reading the links provided by @emboss, it is clear that the seed, however random it maybe, should not be used with java.util.Random. It is very easy to calculate the seed by observing the output. Go for SecureRandom - Use Native PRNG (as given in the link above) because it takes random values from the /dev/random file for each call to nextBytes(). This way an attacker observing the output will not be able to make out anything unless he is controlling the contents of the /dev/random file(which is very unlikely) The sha1 prng algorithm calculates the seed only once and if your VM is running for months using the same seed, it might be cracked by an attacker who is passively observing the output. NOTE - If you are calling the nextBytes() faster than your os is able to write random bytes(entropy) into the /dev/random, you might land into trouble when using NATIVE PRNG. In that case use a SHA1 PRNG instance of SecureRandom and every few minutes(or some interval), seed this instance with the value from nextBytes() of a NATIVE PRNG instance of SecureRandom. Running these two parallely will ensure that you are seeding regularly with true random values, while also not exhausting the entropy obtained by the Operating System.

其他回答

如果使用相同的种子运行两次java.util.Random.nextLong(),它将生成相同的数字。出于安全原因,你想要坚持使用java.security. securerrandom,因为它很难预测。

这两个类是相似的,我认为你只需要用重构工具把Random改成SecureRandom,你现有的大部分代码就可以工作了。

我将尽量使用非常基本的词,以便您可以轻松理解随机和安全随机之间的区别以及安全随机类的重要性。

想知道OTP(一次性密码)是如何生成的吗? 为了生成OTP,我们也使用Random和SecureRandom类。现在,为了让你的OTP强大,安全随机更好,因为它需要2^128次尝试来破解OTP,这在目前的机器上几乎是不可能的,但如果使用随机类,那么你的OTP可以被那些可能破坏你的数据的人破解,因为它只需要2^48次尝试就可以破解。

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位是负的,需要一些努力,但人们可以发现这一点。

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

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

其他虚拟机实现也做同样的事情。 JDK未来版本中Random类的实现仍然使用securerrandom类

因此,遵循文档建议并直接使用SecureRandom是更好的选择。

A random has only 48 bits where as SecureRandom can have upto 128 bits. So the chances of repeating in securerandom is very small. Random uses the system clock as the seed/or to generate the seed. So they can be reproduced easily if the attacker knows the time at which the seed was generated. But SecureRandom takes Random Data from your os(they can be interval between keystrokes etc - most os collect these data store them in files - /dev/random and /dev/urandom in case of linux/solaris) and uses that as the seed. So if the small token size is okay(in case of Random), you can continue using your code without any changes, since you are using SecureRandom to generate the seed. But if you want larger tokens(which cannot be subject to brute force attacks) go with SecureRandom - In case of random just 2^48 attempts are required, with todays advanced cpu's it is possible to break it in practical time. But for securerandom 2^128 attempts will be required, which will take years and years to break even with today's advanced machines. See this link for more details. EDIT After reading the links provided by @emboss, it is clear that the seed, however random it maybe, should not be used with java.util.Random. It is very easy to calculate the seed by observing the output. Go for SecureRandom - Use Native PRNG (as given in the link above) because it takes random values from the /dev/random file for each call to nextBytes(). This way an attacker observing the output will not be able to make out anything unless he is controlling the contents of the /dev/random file(which is very unlikely) The sha1 prng algorithm calculates the seed only once and if your VM is running for months using the same seed, it might be cracked by an attacker who is passively observing the output. NOTE - If you are calling the nextBytes() faster than your os is able to write random bytes(entropy) into the /dev/random, you might land into trouble when using NATIVE PRNG. In that case use a SHA1 PRNG instance of SecureRandom and every few minutes(or some interval), seed this instance with the value from nextBytes() of a NATIVE PRNG instance of SecureRandom. Running these two parallely will ensure that you are seeding regularly with true random values, while also not exhausting the entropy obtained by the Operating System.