赞
踩
伪随机数生成器使用确定的数学算法产生具备良好统计属性的数字序列,但实际上这种数字序列并非具备真正的随机特性。
伪随机数生成器通常以一个种子值为起始,每次计算使用当前种子值生成一个输出及一个新种子,这个新种子会被用于下次计算。
Java 提供的伪随机数生成器 java.util.Random
的不同实例如果使用相同种子值创建,则后续产生的随机数总会重复,如:
@Test
public void test() {
long seed = System.currentTimeMillis();
Random random1 = new Random(seed);
Random random2 = new Random(seed);
for (int i = 0; i < 10; i++) {
System.out.println(random1.nextInt() + "=" + random2.nextInt());
}
}
测试结果:
584397915=584397915
1101630039=1101630039
-1214277036=-1214277036
-119968434=-119968434
-262827225=-262827225
-1539565323=-1539565323
1391920860=1391920860
-922846780=-922846780
2080924613=2080924613
2114543521=2114543521
以上场景也正好模拟了 java.util.Random
的常用场景,使用不带任何参数的 Random
构造函数生成 Random
实例时,系统会使用系统时钟的当前时间作为种子值,那么系统在初始化或重启时生成的 Random
实例的种子值可能都是相同的。攻击者可以在系统中植入监听并构建相应的查询表预测将要使用的种子值,因此安全场景中不能使用 java.util.Random
。
安全场景中建议使用 java.security.SecureRandom
,SecureRandom
提供加密的强随机数生成器(RNG),实际上 SecureRandom
继承自 java.util.Random
,本质上仍然是伪随机数生成器原理,在种子值确定的情况下生成的随机数也一样,区别在于 SecureRandom
使用的种子值不单单是系统时钟的当前时间,增强了种子值的不可预测性,因此比 Random
安全,不过也因此会带来一定的性能开销,因此 Random
与 SecureRandom
的选择取决于真实使用场景,安全场景中尽量使用 SecureRandom
。
java.security.SecureRandom
正确使用示例:
SecureRandom
实例。@Test
public void test() {
SecureRandom random1 = new SecureRandom();
SecureRandom random2 = new SecureRandom();
for (int i = 0; i < 10; i++) {
System.out.println(random1.nextInt() + "!=" + random2.nextInt());
}
}
SecureRandom
内置两种随机数算法:NativePRNG
和 SHA1PRNG
,可以指定随机数算法生成SecureRandom
实例。SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
这 java.security.SecureRandom
是更安全的版本 java.util.Random
,它在 Java 中提供了加密安全伪随机数生成器 (CSPRNG)。
java.security.SecureRandom
总是优先于 java.util.Random
用于生成敏感随机数,例如在密码应用程序中生成加密密钥或在 Web 服务器上或在密码生成器中生成会话 ID 以创建高度安全的密码。
这里有几个原因 java.security.SecureRandom
应在敏感应用中使用:
java.util.Random
实现使用线性同余生成器 (LCG) 来生成高度可预测的伪随机值。另一方面,大多数 java.security.SecureRandom
实现使用伪随机数生成器 (PRNG),它使用确定性算法从真正的随机种子生成伪随机序列。
java.util.Random
使用种子的系统时间。 java.security.SecureRandom
从底层操作系统获取随机数据。在 Linux/Solaris 中,随机数是通过从熵池中读取来创建的 /dev/random
和 /dev/urandom
文件。
如果两个实例 java.util.Random
使用相同的种子创建,并且为每个进行相同的方法调用序列,它们将生成并返回相同的数字序列。情况并非如此 java.security.SecureRandom
,它从操作系统获得的熵源中播种,例如 I/O 事件的时间,这实际上是无法检测到的。
java.util.Random
类使用 48
-位种子,而 java.security.SecureRandom
通常使用一个 128
-位或 160
位种子。因此,只有 248
需要尝试打破 Random
类,在现代计算机上甚至可能不需要一秒钟。另一方面, 2128
或者 2160
将需要尝试 SecureRandom
,这将需要数年才能达到今天的 CPU 计算能力。
java.security.SecureRandom
产生不确定的输出,因为它不依赖于系统时钟的种子值。所以不可能预测之前和未来的随机数。 java.util.Random
另一方面,使用系统时钟作为种子,如果生成种子的时间已知,则可以轻松复制。
LCG 速度非常快,通常只需要 32-
或者 64-
位来保持状态。另一方面,创建一个 java.security.SecureRandom
实例比创建一个非常昂贵 java.util.Random
实例和关于 30-50
比慢几倍 Random
.
此外,该 generateSeed()
和 nextBytes()
的方法 SecureRandom
如果没有足够的熵可用并且熵正在收集,则可能会在 Linux 上被阻止 /dev/random
文件。这是什么 Wikipedia 不得不说:
生成器保持对熵池中噪声总位数的估计。从这个熵池中创建随机数。阅读时, /dev/random
设备将仅返回熵池中估计的噪声位数内的随机字节。 /dev/random
应该适用于需要非常高质量随机性的用途,例如一次性填充或密钥生成。当熵池为空时,从 /dev/random
将阻塞,直到收集到额外的环境噪音。
值得注意的是 java.security.SecureRandom
是一个子类 java.util.Random
并继承所有 nextX()
方法,其中 X 可以是 Boolean、Double、Float、Gaussian、Int 和 Long。
初始化 java.util.Random
和 java.security.SecureRandom
:
Random
可以初始化为:
Random rand = new Random(System.nanoTime());
SecureRandom
可以初始化为:
SecureRandom rand = new SecureRandom(SecureRandom.getSeed(20));
在这里,数 20
表示 160
-位种子为 20
字节 = 160
位。
java.security.SecureRandom
在安全敏感的应用程序中获得密码安全的伪随机数生成器。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。