0
点赞
收藏
分享

微信扫一扫

Java 中的随机数 | Java Debug 笔记

从伪随机数生成器 & 真随机数生成器的角度看 java.util.Random & java.security.SecureRandom 。


伪随机数生成器 & 真随机数生成器

伪随机数生成器:又被称为确定性随机比特生成器,是一个生成数字序列的算法,其特性近似于随机数序列的特性。完全由一个初始值决定,这个初始值被称为 PRNG 的随机种子。同样的初始值总是生成同样的序列。

真随机数生成器:又称为硬件随机数生成器,是一种通过物理过程而不是计算机程序来生成随机数的设备。这样的设备通常是基于一些能生成低等级、统计学随机的“噪声”信号的微观现象,如热力学噪声、光电效应和量子现象。这些物理过程在理论上是完全不可预测的。


java.util.Random

平时用的最多的类就是 java.util.Random 了,通过操作符 new 就可以获取一个实例,调用诸多方法获取随机数,十分方便。

Random r = new Random();
System.out.println(r.nextInt());
System.out.println(r.nextFloat());
System.out.println(r.nextBoolean());

每次运行获取的数值都不一样,视乎满足了我们想要的“随机”的要求。但其实通过 java.util.Random 获取到的都是伪随机数,只是我们通常不指定随机种子直接使用。

由相关的构造方法实现可知,当不指定随机种子创建 java.util.Random 实例的时候会自动生成一个与当前时间相关的数值作为随机种子。

public Random() {
this(seedUniquifier() ^ System.nanoTime());
}

public Random(long seed) {
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
// subclass might have overridden setSeed
this.seed = new AtomicLong();
setSeed(seed);
}
}

这就是为什么我们使用 java.util.Random 获取随机数还能得到“不确定”随机序列的原因,一旦我们指定随机数创建 java.util.Random 实例时,那么产生的随机序列就是完全确定的:

Random r = new Random(123);
System.out.println(r.nextInt());
System.out.println(r.nextFloat());
System.out.println(r.nextBoolean());

因而 java.util.Random 产生随机序列并不可靠。这时候可能会有读者有疑问,不指定随机种子使用不就可以了,让 java.util.Random 始终使用与时间相关的数值作为随机种子,这样产生的随机序列也是不可预测。其实不然,因为一旦你使用的随机种子是有规律可复现的,那么产生的随机序列也就是有规律可复现的,也就是不能做到真正的“随机”。

举个????,假如你使用 java.util.Random 产生的随机序列(不指定随机种子)作为加密的密钥原料,那么我完全可以通过时钟回拨+暴力枚举的方式获取到当时用作生成密钥所用到的随机序列。最终整个加密流程会因为使用了伪随机算法生成密钥导致可攻破。


java.security.SecureRandom

我们知道真随机算法依靠的是“噪声”来产生随机数。通常有热力学噪声、光电效应和量子现象产生。那么计算机如果要产生真正意义上的随机数只能是依靠量子力学,通过量子比特的量子叠加来产生,但是目前量子计算机还没面世,那么我们如何通过计算机产生一个较为可靠的“随机数”呢?java 提供了 java.security.SecureRandom 。

其实 java.security.SecureRandom 也是继承 java.util.Random ,只不过不能再指定真正用于产生序列的随机种子。 java.security.SecureRandom 能够通过 new 操作符获取普通的随机数生成器,也能通过静态方法 SecureRandom.getInstanceStrong(); 获取健壮的随机数生成器。

通过 java.security.SecureRandom 即使再指定相同的 seed 也无法产生可复现的确定随机序列:

SecureRandom r = new SecureRandom(new byte[]{123});
// SecureRandom r = SecureRandom.getInstanceStrong();
System.out.println(r.nextInt());
System.out.println(r.nextFloat());
System.out.println(r.nextBoolean());

使用 java.security.SecureRandom 虽然不是真正意义上的“真随机”,但相对于 java.util.Random 确实更为安全。所以在涉及加密流程需要用到随机数,建议使用 java.security.SecureRandom 。

举报

相关推荐

0 条评论