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


202

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

这些令牌的用途非常敏感-用于会话ID,密码重置链接等。因此,它们确实需要进行密码随机处理,以避免有人猜测或对它们进行暴力破解。令牌是“长”的,因此它是64位长。

该代码当前使用java.util.Random该类来生成这些令牌。该文档java.util.Random明确规定如下:

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

但是,代码当前使用的方式java.util.Random是-实例化java.security.SecureRandom该类,然后使用该SecureRandom.nextLong()方法获取用于实例化java.util.Random该类的种子。然后使用java.util.Random.nextLong()方法生成令牌。

所以,我现在的问题是-仍然java.util.Random使用种子进行播种还是不安全的java.security.SecureRandom?我是否需要修改代码,使其java.security.SecureRandom专门用于生成令牌?

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


14
一旦设定种子,java.util.Random的输出就是确定性的数字序列。您可能不想要那样。
PeterŠtibraný2012年

1
代码是Random在启动时播种一次,还是为每个令牌播种新的种子?希望这是一个愚蠢的问题,但我想我会检查一下。
Tom Anderson

8
随机仅具有一个48位的内部状态和后2 ^ 48调用nextLong(),这意味着它不会产生所有可能将重复longdouble值。
彼得·劳瑞

3
还有另一个严重的问题。64位意味着1.84 * 10 ^ 19种可能的组合,该组合太少而无法承受复杂的攻击。那里的机器在60小时内以每秒90 * 10 ^ 9的密钥破解了56位DES代码(少了256倍)。使用128位或两长!
Thorsten S.

Answers:


232

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

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

这是线性同余伪随机数生成器,由DH Lehmer定义,由Donald E. Knuth在 计算机编程艺术,第3卷: 半数值算法,第3.2.1节中进行了描述。

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

Hugo Krawczyk就如何预测这些LCG撰写了一篇相当不错的论文(“如何预测同余生成器”)。如果您很幸运和感兴趣,您仍然可以在网上找到它的免费下载版本。而且,还有更多的研究清楚地表明,绝对不应将LCG用于对安全性至关重要的目的。这也意味着您的随机数现在可以预测的,对于会话ID等来说,这是您所不希望的。

如何打破线性同余生成器

假设攻击者在一个完整的周期后必须等待LCG重复,这一假设是错误的。即使具有最佳周期(其递归关系中的模数m),也很容易在比整个周期少的时间内预测未来值。毕竟,这只是一堆模块化方程式,需要解决,只要您观察到足够的LCG输出值,就可以轻松实现。

使用“更好”的种子无法提高安全性。不管您是否使用SecureRandom由多次滚动产生的随机值作为种子进行播种甚至生成该值都没有关系。

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

结论

替换您当前的代码。SecureRandom专门使用。然后,至少您可以保证结果很难预测。如果您需要加密安全的PRNG的属性(在您的情况下,这就是您想要的),那么您就SecureRandom只能使用。聪明地改变应该使用的方式几乎总是会导致安全性降低。


4
很有帮助,也许您还可以解释SecureRandom的工作原理(就像您解释Random的工作原理一样)
。–

4
这违反了
secureRandom

我知道,很难学到这一课。但是强硬的密码和难以找到的来源效果很好。Notch可以从中学到一些东西(他将用户密码编码为.lastlogin文件,并使用“ passwordfile”作为密钥进行基本加密编码)
Azulflame 2012年

1
真正的问题是:如果Java可以使用类似的API生成更安全的prng,为什么他们不只是替换损坏的prng?
Joel Coehoorn 2012年

11
@JoelCoehoorn并不是Random坏了-应该只在不同的场景中使用它。当然,您可以始终使用SecureRandom。但总的来说,SecureRandom它比纯慢Random。在某些情况下,您只对良好的统计属性和出色的性能感兴趣,但您并不真正在意安全性:蒙特卡洛模拟就是一个很好的例子。我在类似的答案中对此发表了评论,也许您会发现它很有用。
浮雕

72

随机数只有48位,而SecureRandom最多可以有128位。因此,在安全随机中重复的机会很小。

Randomsystem clock用作种子/或生成种子。因此,如果攻击者知道生成种子的时间,则可以轻松地复制它们。但是SecureRandom的需要Random Data从你的os( -大多数操作系统收集这些数据并将其存储在文件中-他们可以等按键的间隔 /dev/random and /dev/urandom in case of linux/solaris),并将其用作种子。
因此,如果令牌的大小很小(在Random的情况下),则可以继续使用代码而无需进行任何更改,因为您正在使用SecureRandom生成种子。但是,如果您想要更大的令牌(不能接受brute force attacks),请使用SecureRandom-
随机使用)2^48需要尝试,使用当今的高级CPU可以在实际时间内将其中断。但是,为了保证安全性,2^128将需要尝试,而要使用当今的先进机器,甚至要花费数年甚至数年才能达到收支平衡。(如上面的链接中所述),因为它从中获取随机值

有关更多详细信息,请参见此链接。
编辑
阅读@emboss提供的链接后,很明显,种子,无论它是随机的,都不应与java.util.Random一起使用。通过观察输出来计算种子非常容易。

选择SecureRandom-使用本机PRNG。这样,攻击者观察输出将不能够做出来的任何东西,除非他控制的内容文件(这是非常不可能的) 的SHA1 PRNG算法计算的种子只有一次,如果你的虚拟机使用相同的数月运行种子,它可能被被动观察输出的攻击者破解。注意 -如果您正在拨打/dev/random对于每次调用,文件中nextBytes()/dev/random


nextBytes()速度比os向os中写入随机字节(熵)速度快/dev/random,那么使用NATIVE PRNG可能会遇到麻烦 。在这种情况下,请使用SecureRandom的SHA1 PRNG实例,并每隔几分钟(或一定间隔),使用以下值播种该实例:nextBytes()的SecureRandom的NATIVE PRNG实例。运行这两个并行操作将确保您定期使用真实的随机值播种,同时也不会耗尽操作系统获得的熵。


预测a所需的时间远远少于2 ^ 48 Random,OP根本不应使用Random
浮雕

@emboss:我说的是蛮力。
Ashwin 2012年

1
注意Linux:它可以达到熵耗尽(VM中比硬件更多)!查看/proc/sys/kernel/random/entropy_avail并检查一些线程转储,以便在继续阅读时无需等待太长时间/dev/random
Yves Martin

2
请注意,默认情况下,Oracle JRE(至少为1.7)与/ dev / urandom一起使用,而不与/ dev / random一起使用,因此答案的后缀不再正确。验证$ JAVA_HOME / lib / security / java.security中的securerandom.source属性
Boaz

1
我们的java.security文件具有securerandom.source = file:/ dev / urandom而不是file:/// dev / urandom(冒号在文件协议后面加上两个斜线,然后在文件系统的根后面再加上一个斜线),导致其回退到/ dev / random,这会导致熵池耗尽。无法编辑它,因此必须在应用程序启动时将系统属性java.security.egd设置为正确的属性。
maxpolk

11

如果java.util.Random.nextLong()使用相同的种子运行两次,它将产生相同的数字。出于安全原因,您要坚持下去,java.security.SecureRandom因为它的可预测性要低得多。

这两个类是相似的,我认为您只需使用重构工具将其更改Random为即可SecureRandom,并且大多数现有代码都可以使用。


11
如果您使用任何PRNG的两个实例并以相同的值进行播种,则始终会获得相同的随机数,即使使用SecureRandom也不会更改该随机数。如果您知道种子,所有PRNG都是确定性的,因此是可预测的。
罗伯特

1
有不同的SecureRandom实现,有些是PRNG,有些则不是。另一方面,java.util.Random始终是PRNG(在其Javadoc中定义)。
PeterŠtibraný2012年

3

如果更改现有代码是可以负担的任务,建议您按照Javadoc中的建议使用SecureRandom类。

即使您发现Random类实现也在内部使用SecureRandom类。您不应认为这是理所当然的:

  1. 其他VM实现也做同样的事情。
  2. 将来的JDK版本中对Random类的实现仍然使用SecureRandom类

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


我不相信最初的问题是说该java.util.Random实现是在SecureRandom内部使用的,而是说他们的代码用来SecureRandom为植入种子Random。不过,到目前为止,我都同意这两个答案。最好SecureRandom避免使用明确的确定性解决方案。
Palpatim 2012年

2

的当前参考实现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);
}

结果的高32位是nextLong()当时种子的位。由于种子的宽度为48位(例如javadoc),因此可以对剩余的16位进行迭代(只需尝试65.536次),以确定产生第二个32位的种子。

一旦知道种子,就可以轻松计算出所有后续标记。

使用nextLong()PNG直接(部分)的秘密的输出,以至于可以用很少的efford来计算整个秘密。危险的!

*如果第二个32位为负数,则需要付出一些努力,但是可以找到答案。


正确。请参阅jazzy.id.au/default/2010/09/20/…了解如何快速破解java.util.random !
ingyhere 2014年

2

种子是没有意义的。一个好的随机发生器在选择的质数上有所不同。每个随机生成器都从一个数字开始,并通过一个“环”进行迭代。这意味着,您将使用旧的内部值从一个数字到另一个数字。但是过了一会儿,您会再次到达起点,然后重新开始。因此,您运行周期。(随机生成器的返回值不是内部值)

如果使用质数创建环,则在完成所有可能数字的完整循环之前,将选择该环中的所有数字。如果您使用非质数,则不会选择所有数字,周期也会更短。

较高的质数意味着更长的周期,然后再次返回第一个元素。因此,安全的随机生成器只有更长的周期,才能再次到达起点,这就是为什么它更安全的原因。您无法像缩短周期那样容易地预测数字的生成。

换句话说:您必须全部替换。


0

我将尝试使用非常基本的词,以便您可以轻松理解Random和secureRandom之间的区别以及SecureRandom类的重要性。

有没有想过如何生成一次性密码(一次性密码)?为了生成OTP,我们也使用Random和SecureRandom类。现在,要使您的OTP变得更强大,SecureRandom会更好,因为它花了2 ^ 128次尝试才能破解OTP,这在当前机器上几乎是不可能的,但是如果使用Random Class,那么您的OTP可能会被可能破坏您数据的人破解。只需2 ^ 48尝试,即可破解。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.