我们一起聊聊 Java 随机数的种子

开发 前端
假设在一个非常空闲的机器上,SecureRandom​使用高熵值可能会使服务卡死,机器没有足够的随机信息,SecureRandom无法生成种子,就难以运行了。

在许多领域,比如模拟、游戏和密码学中,随机数担任非常重要的角色。

然而,在计算机领域,随机数并非完全随机,它们是由模拟随机性的算法(称为伪随机性)生成的。

在Java中,随机种子就是初始化伪随机数生成器(PRNG,Pseudo Random Number Generator)的值。

我们一起探讨下,Java中随机种子的工作原理,以及如何使用它生成可预测的数字序列。

一、什么是随机种子?

随机种子是设置PRNG(伪随机数生成器)内部状态的初始值。

默认情况下,如果我们指定种子值,Java的Random类会使用系统时钟作为种子值。这样做的好处是,确保了每次创建新的Random对象时,生成的数字序列都是不同的,增加了随机性。

如果我们提供特定的种子值,每次都会生成相同的“随机”数字序列。这在我们需要可重复性的情况下非常有用,比如测试、调试或需要结果一致性的模拟场景。

有了种子值之后,PRNG算法会基于种子值生成一系列数字。

每次我们调用nextInt()、nextDouble()或类似方法时,它都会更新生成器的内部状态,从而保证每次生成一个新数字。但是,如果使用相同的种子,生成的数字序列将始终相同。

接下来我们看下这两种情况。

二、不使用种子生成随机数

Java提供了java.util.Random类,用于生成随机数。

当我们创建一个Random实例而不指定种子时,Java会使用系统时钟为生成器设定种子。这意味着每次运行都会产生不同的序列。例如:

import java.util.Random;

public class RandomWithoutSeed {
    public static void main(String[] args) {
        Random random = new Random();
        // 生成7个随机整数
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        Random random2 = new Random();
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random2.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        Random random3 = new Random();
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random3.nextInt(100)); // 0到99之间的随机整数
        }
    }
}

在这个例子中,每次运行都会生成不同的随机整数序列,因为种子是根据当前时间自动设置的。

第一次运行结果是:

76 	9 	11 	77 	67 	91 	91
76 	44 	28 	5 	91 	59 	30
41 	18 	72 	14 	6 	4 	63

在运行一次:

33 	65 	97 	31 	94 	19 	1
97 	2 	40 	58 	9 	33 	57
46 	82 	21 	94 	54 	36 	79

可以看出来,结果基本上符合随机性。(上面的结果只是展示下随机效果,每次运行都会有差异)

三、使用种子生成随机数

当我们提供特定的种子时,生成的数字序列在不同的运行中是可预测且一致的。

import java.util.Random;

public class RandomWithSeed {
    public static void main(String[] args) {
        Random random = new Random(12345L); // 种子设置为12345
        // 生成7个随机整数
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        Random random2 = new Random(12345L); // 种子设置为12345
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random2.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        Random random3 = new Random(12345L); // 种子设置为12345
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random3.nextInt(100)); // 0到99之间的随机整数
        }
    }
}

在这里,Random类的构造函数接受一个种子值作为参数,在这个例子中,种子被设置为12345L(一个特定的长整型值)。

这个种子初始化伪随机数生成器(PRNG),重要的是,它确保如果程序使用相同的种子运行,将始终生成相同的数字序列。

第一次运行结果是:

51 	80 	41 	28 	55 	84 	75
51 	80 	41 	28 	55 	84 	75
51 	80 	41 	28 	55 	84 	75

再来一次还是这样:

51 	80 	41 	28 	55 	84 	75
51 	80 	41 	28 	55 	84 	75
51 	80 	41 	28 	55 	84 	75

所以说,“随机”是可以操纵的。

四、使用SecureRandom

在密码学应用中,使用可预测的随机数可能会导致安全漏洞。

Java提供了SecureRandom类用于生成密码学安全的随机数。

看名字就知道,SecureRandom安全等级高一些。

import java.security.SecureRandom;

public class SecureRandomExample {
    public static void main(String[] args) throws Exception {
        SecureRandom random = new SecureRandom(new byte[] {1, 2, 3, 4, 5});
        // 生成7个随机整数
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        SecureRandom random2 = new SecureRandom(new byte[] {1, 2, 3, 4, 5});
        // 生成7个随机整数
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random2.nextInt(100)); // 0到99之间的随机整数
        }
        System.out.println();

        SecureRandom random3 = new SecureRandom(new byte[] {1, 2, 3, 4, 5});
        // 生成7个随机整数
        for (int i = 0; i < 7; i++) {
            System.out.format("%d \t", random3.nextInt(100)); // 0到99之间的随机整数
        }
    }
}

上面的例子中,我们传入相同的种子,运行结果也是随机的。

第一次运行:

78 	68 	56 	24 	73 	13 	88
24 	14 	20 	69 	25 	4 	61
25 	8 	32 	39 	25 	16 	87

第二次运行:

4 	35 	46 	26 	48 	92 	66
83 	92 	28 	64 	13 	75 	44
60 	79 	81 	52 	7 	66 	11

结果也是足够随机的。(上面的结果只是展示下随机效果,每次运行都会有差异)

SecureRandom使用高熵值的源来初始化其内部状态。熵是对不确定性或随机性的度量,高熵源意味着具有更多的随机性。常见的熵源包括:

  • 操作系统提供的随机数据:许多操作系统都有内置的随机数生成器,它们从硬件设备(如鼠标移动、键盘敲击时间间隔、磁盘 I/O 操作等)收集随机事件产生的数据,这些数据具有较高的随机性,SecureRandom可以从中获取种子或随机数据来初始化自身。
  • 硬件随机数生成器:某些计算机系统配备了专门的硬件设备来生成真正的随机数,例如基于热噪声、放射性衰变等物理现象的硬件随机数生成器。这些硬件设备能够产生高质量的随机数,SecureRandom可以直接使用或结合这些硬件生成的随机数来增强随机性。

SecureRandom会维护一个内部状态,该状态在每次生成随机数时都会更新。新生成的随机数不仅取决于当前的熵源数据,还与之前的内部状态有关。这种状态更新机制使得生成的随机数序列更加难以预测,即使攻击者获取了部分随机数,也难以推断出后续的随机数。

与普通的Random类不同,SecureRandom对种子的管理更为严格。它可以自动从可靠的熵源获取种子,以确保每次初始化时都有足够的随机性。

虽然允许用户提供种子,但通常建议让系统自动管理种子,以充分利用高质量的熵源。

需要注意的是,假设在一个非常空闲的机器上,SecureRandom使用高熵值可能会使服务卡死,机器没有足够的随机信息,SecureRandom无法生成种子,就难以运行了。

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2022-12-06 08:12:11

Java关键字

2022-07-29 08:17:46

Java对象内存

2022-10-08 00:00:05

SQL机制结构

2023-08-04 08:20:56

DockerfileDocker工具

2023-06-30 08:18:51

敏捷开发模式

2023-08-10 08:28:46

网络编程通信

2022-05-24 08:21:16

数据安全API

2023-09-10 21:42:31

2023-04-26 07:30:00

promptUI非结构化

2024-02-20 21:34:16

循环GolangGo

2021-08-27 07:06:10

IOJava抽象

2023-11-10 08:04:43

Java 17Java 11JDK

2023-08-02 08:35:54

文件操作数据源

2024-09-09 08:53:56

2024-06-14 09:32:12

2022-09-08 08:50:17

SSDOracleCPU

2022-09-22 08:06:29

计算机平板微信

2023-03-26 23:47:32

Go内存模型

2022-02-23 08:41:58

NATIPv4IPv6

2021-08-12 07:49:24

mysql
点赞
收藏

51CTO技术栈公众号