我們一起聊聊 Java 隨機(jī)數(shù)的種子
在許多領(lǐng)域,比如模擬、游戲和密碼學(xué)中,隨機(jī)數(shù)擔(dān)任非常重要的角色。
然而,在計算機(jī)領(lǐng)域,隨機(jī)數(shù)并非完全隨機(jī),它們是由模擬隨機(jī)性的算法(稱為偽隨機(jī)性)生成的。
在Java中,隨機(jī)種子就是初始化偽隨機(jī)數(shù)生成器(PRNG,Pseudo Random Number Generator)的值。
我們一起探討下,Java中隨機(jī)種子的工作原理,以及如何使用它生成可預(yù)測的數(shù)字序列。
一、什么是隨機(jī)種子?
隨機(jī)種子是設(shè)置PRNG(偽隨機(jī)數(shù)生成器)內(nèi)部狀態(tài)的初始值。
默認(rèn)情況下,如果我們指定種子值,Java的Random類會使用系統(tǒng)時鐘作為種子值。這樣做的好處是,確保了每次創(chuàng)建新的Random對象時,生成的數(shù)字序列都是不同的,增加了隨機(jī)性。
如果我們提供特定的種子值,每次都會生成相同的“隨機(jī)”數(shù)字序列。這在我們需要可重復(fù)性的情況下非常有用,比如測試、調(diào)試或需要結(jié)果一致性的模擬場景。
有了種子值之后,PRNG算法會基于種子值生成一系列數(shù)字。
每次我們調(diào)用nextInt()、nextDouble()或類似方法時,它都會更新生成器的內(nèi)部狀態(tài),從而保證每次生成一個新數(shù)字。但是,如果使用相同的種子,生成的數(shù)字序列將始終相同。
接下來我們看下這兩種情況。
二、不使用種子生成隨機(jī)數(shù)
Java提供了java.util.Random類,用于生成隨機(jī)數(shù)。
當(dāng)我們創(chuàng)建一個Random實(shí)例而不指定種子時,Java會使用系統(tǒng)時鐘為生成器設(shè)定種子。這意味著每次運(yùn)行都會產(chǎn)生不同的序列。例如:
import java.util.Random;
public class RandomWithoutSeed {
public static void main(String[] args) {
Random random = new Random();
// 生成7個隨機(jī)整數(shù)
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
Random random2 = new Random();
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random2.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
Random random3 = new Random();
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random3.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
}
}
在這個例子中,每次運(yùn)行都會生成不同的隨機(jī)整數(shù)序列,因?yàn)榉N子是根據(jù)當(dāng)前時間自動設(shè)置的。
第一次運(yùn)行結(jié)果是:
76 9 11 77 67 91 91
76 44 28 5 91 59 30
41 18 72 14 6 4 63
在運(yùn)行一次:
33 65 97 31 94 19 1
97 2 40 58 9 33 57
46 82 21 94 54 36 79
可以看出來,結(jié)果基本上符合隨機(jī)性。(上面的結(jié)果只是展示下隨機(jī)效果,每次運(yùn)行都會有差異)
三、使用種子生成隨機(jī)數(shù)
當(dāng)我們提供特定的種子時,生成的數(shù)字序列在不同的運(yùn)行中是可預(yù)測且一致的。
import java.util.Random;
public class RandomWithSeed {
public static void main(String[] args) {
Random random = new Random(12345L); // 種子設(shè)置為12345
// 生成7個隨機(jī)整數(shù)
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
Random random2 = new Random(12345L); // 種子設(shè)置為12345
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random2.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
Random random3 = new Random(12345L); // 種子設(shè)置為12345
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random3.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
}
}
在這里,Random類的構(gòu)造函數(shù)接受一個種子值作為參數(shù),在這個例子中,種子被設(shè)置為12345L(一個特定的長整型值)。
這個種子初始化偽隨機(jī)數(shù)生成器(PRNG),重要的是,它確保如果程序使用相同的種子運(yùn)行,將始終生成相同的數(shù)字序列。
第一次運(yùn)行結(jié)果是:
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
所以說,“隨機(jī)”是可以操縱的。
四、使用SecureRandom
在密碼學(xué)應(yīng)用中,使用可預(yù)測的隨機(jī)數(shù)可能會導(dǎo)致安全漏洞。
Java提供了SecureRandom類用于生成密碼學(xué)安全的隨機(jī)數(shù)。
看名字就知道,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個隨機(jī)整數(shù)
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
SecureRandom random2 = new SecureRandom(new byte[] {1, 2, 3, 4, 5});
// 生成7個隨機(jī)整數(shù)
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random2.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
System.out.println();
SecureRandom random3 = new SecureRandom(new byte[] {1, 2, 3, 4, 5});
// 生成7個隨機(jī)整數(shù)
for (int i = 0; i < 7; i++) {
System.out.format("%d \t", random3.nextInt(100)); // 0到99之間的隨機(jī)整數(shù)
}
}
}
上面的例子中,我們傳入相同的種子,運(yùn)行結(jié)果也是隨機(jī)的。
第一次運(yùn)行:
78 68 56 24 73 13 88
24 14 20 69 25 4 61
25 8 32 39 25 16 87
第二次運(yùn)行:
4 35 46 26 48 92 66
83 92 28 64 13 75 44
60 79 81 52 7 66 11
結(jié)果也是足夠隨機(jī)的。(上面的結(jié)果只是展示下隨機(jī)效果,每次運(yùn)行都會有差異)
SecureRandom使用高熵值的源來初始化其內(nèi)部狀態(tài)。熵是對不確定性或隨機(jī)性的度量,高熵源意味著具有更多的隨機(jī)性。常見的熵源包括:
- 操作系統(tǒng)提供的隨機(jī)數(shù)據(jù):許多操作系統(tǒng)都有內(nèi)置的隨機(jī)數(shù)生成器,它們從硬件設(shè)備(如鼠標(biāo)移動、鍵盤敲擊時間間隔、磁盤 I/O 操作等)收集隨機(jī)事件產(chǎn)生的數(shù)據(jù),這些數(shù)據(jù)具有較高的隨機(jī)性,SecureRandom可以從中獲取種子或隨機(jī)數(shù)據(jù)來初始化自身。
- 硬件隨機(jī)數(shù)生成器:某些計算機(jī)系統(tǒng)配備了專門的硬件設(shè)備來生成真正的隨機(jī)數(shù),例如基于熱噪聲、放射性衰變等物理現(xiàn)象的硬件隨機(jī)數(shù)生成器。這些硬件設(shè)備能夠產(chǎn)生高質(zhì)量的隨機(jī)數(shù),SecureRandom可以直接使用或結(jié)合這些硬件生成的隨機(jī)數(shù)來增強(qiáng)隨機(jī)性。
SecureRandom會維護(hù)一個內(nèi)部狀態(tài),該狀態(tài)在每次生成隨機(jī)數(shù)時都會更新。新生成的隨機(jī)數(shù)不僅取決于當(dāng)前的熵源數(shù)據(jù),還與之前的內(nèi)部狀態(tài)有關(guān)。這種狀態(tài)更新機(jī)制使得生成的隨機(jī)數(shù)序列更加難以預(yù)測,即使攻擊者獲取了部分隨機(jī)數(shù),也難以推斷出后續(xù)的隨機(jī)數(shù)。
與普通的Random類不同,SecureRandom對種子的管理更為嚴(yán)格。它可以自動從可靠的熵源獲取種子,以確保每次初始化時都有足夠的隨機(jī)性。
雖然允許用戶提供種子,但通常建議讓系統(tǒng)自動管理種子,以充分利用高質(zhì)量的熵源。
需要注意的是,假設(shè)在一個非??臻e的機(jī)器上,SecureRandom使用高熵值可能會使服務(wù)卡死,機(jī)器沒有足夠的隨機(jī)信息,SecureRandom無法生成種子,就難以運(yùn)行了。