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

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


当前回答

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

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

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

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

Hugo Krawczyk写了一篇关于如何预测这些lcg的很好的论文(“如何预测同分生成器”)。如果你足够幸运并且感兴趣,你还可以在网上找到一个免费的、可下载的版本。还有更多的研究清楚地表明,您永远不应该将LCG用于安全关键目的。这也意味着你的随机数现在是可预测的,这是你不希望会话id之类的东西。

如何打破一个线性同余发生器

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

安全性不会因为“更好”的种子而提高。不管你是使用SecureRandom生成的随机值,还是通过多次掷骰子生成该值,这都无关紧要。

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

结论

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

其他回答

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Hugo Krawczyk写了一篇关于如何预测这些lcg的很好的论文(“如何预测同分生成器”)。如果你足够幸运并且感兴趣,你还可以在网上找到一个免费的、可下载的版本。还有更多的研究清楚地表明,您永远不应该将LCG用于安全关键目的。这也意味着你的随机数现在是可预测的,这是你不希望会话id之类的东西。

如何打破一个线性同余发生器

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

安全性不会因为“更好”的种子而提高。不管你是使用SecureRandom生成的随机值,还是通过多次掷骰子生成该值,这都无关紧要。

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

结论

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