我的团队交出了一些服务器端代码(在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来生成令牌?

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


当前回答

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

其他回答

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

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

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

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

种子毫无意义。一个好的随机发生器的不同之处在于所选择的质数。每个随机生成器都从一个数字开始,并通过一个“环”进行迭代。也就是说,从一个数到下一个数,内部值不变。但过了一段时间,你又回到了起点,一切又重新开始。所以你运行循环。(随机生成器的返回值不是内部值)

如果你用一个质数来创建一个环,那么在你完成一个完整的循环之前,这个环中的所有数字都会被选中。如果取非质数,不是所有的数都被选中,周期会更短。

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

换句话说:你必须替换所有。

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是更好的选择。