Linux 上的 SecureRandom 產生器-是否阻塞?
1. 引言
高品質隨機數的可用性對於所有安全和密碼應用都至關重要。通常的偽隨機數產生器提供的是可預測且可重複的序列。
相較之下,Java SecureRandom使用演算法從外部來源抽取隨機數來增加隨機性。我們將這個隨機數池稱為熵源。但是,如果熵源耗盡,則可能會出現速度變慢或阻塞的情況。
在本教程中,我們將討論 Linux 系統中的阻塞演算法和非阻塞演算法。
2. 預設演算法
我們可以使用建構函式建立一個SecureRandom實例:
SecureRandom secureRandom = new SecureRandom();
Java 虛擬機器 (JVM) 接著提供預設演算法,該演算法可在任何作業系統上使用。
**選擇取決於java.security屬性檔案中的設定、JVM 選項java.security.egd以及現有的安全性提供者**。
3. 預定義演算法
Java 文件列出了我們可以直接使用的標準演算法。我們在呼叫getInstance()方法時需要指定所需的演算法:
SecureRandom random = SecureRandom.getInstance("NativePRNG");
當請求某個特定演算法時,如果請求的演算法不存在,我們可能會收到NoSuchAlgorithmException異常。
讓我們來檢查所有預先定義的演算法:
public class SecureRandomAvailableAlgorithms {
static String [] algorithmNames = {"NativePRNG", "NativePRNGBlocking", "NativePRNGNonBlocking",
"PKCS11", "SHA1PRNG", "Windows-PRNG"};
public static void main(String[] args) {
for (int i = 0; i < algorithmNames.length; i++)
{
String name = algorithmNames[i];
Boolean isAvailable = true;
try {
SecureRandom random = SecureRandom.getInstance(name);
} catch (NoSuchAlgorithmException e) {
isAvailable = false;
}
System.out.println("Algorithm " + name + (isAvailable ? " is" : " isn't") + " available");
}
}
}
輸出結果可能如下所示(在 Linux 系統上):
Algorithm NativePRNG is available
Algorithm NativePRNGBlocking is available
Algorithm NativePRNGNonBlocking is available
Algorithm PKCS11 isn't available
Algorithm SHA1PRNG is available
Algorithm Windows-PRNG isn't available
4. Linux 筆記
Linux作業系統提供了兩個隨機數字來源: /dev/random和/dev/urandom 。前者是阻塞式的,後者是非阻塞式的。
然而,從核心版本 5.19 開始,我們就無需擔心可用熵的問題了。除了啟動初期階段之外,無論我們使用/dev/random或/dev/urandom ,在產生隨機數時都不會遇到阻塞。
另一方面, /dev/urandom源的隨機性已經足以滿足所有實際應用的需求,而且它從不阻塞。
考慮到現代 Linux 系統的發展,我們可以安全地使用/dev/urandom設備和相應的演算法 – NativePRNGNonBlocking 。
上述更改影響了熵值的報告方式。我們可以檢查entropy_avail核心參數來取得該值。但是,我們將得到相同的預定義數值:
$ sudo sysctl -a | grep entropy_avail
kernel.random.entropy_avail = 256
因此,我們可以忽略舊核心版本的指導原則,這些原則規定該數字應保持在 2000 或 3000 左右。
5. 性能測試
我們可以使用 Java Microbenchmark Harness (JMH) 微基準測試框架來檢查演算法的效能。我們將比較NativePRNGBlocking和NativePRNGNonBlocking演算法。
讓我們產生 20,000 個大小為 256 位元組的隨機樣本:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class SecureRandomPerformanceTest {
SecureRandom randomNativePRNGBlocking;
SecureRandom randomNativePRNGNonBlocking;
final int NBYTES = 256;
final int NSAMPLES = 20_000;
@Setup(Level.Trial)
public void setup() throws NoSuchAlgorithmException {
randomNativePRNGBlocking = SecureRandom.getInstance("NativePRNGBlocking");
randomNativePRNGNonBlocking = SecureRandom.getInstance("NativePRNGNonBlocking");
}
@Benchmark
public void measureTimePRNGBlocking() {
byte[] randomBytes = new byte[NBYTES];
for (int i = 0; i < NSAMPLES; i++)
{
randomNativePRNGBlocking.nextBytes(randomBytes);
}
}
@Benchmark
public void measureTimePRNGNonBlocking() {
byte[] randomBytes = new byte[NBYTES];
for (int i = 0; i < NSAMPLES; i++)
{
randomNativePRNGNonBlocking.nextBytes(randomBytes);
}
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
讓我們來看看在 Ubuntu 22.04.5 LTS 和 6.8.0 核心版本上獲得的結果:
Benchmark Mode Cnt Score Error Units
SecureRandomPerformanceTest.measureTimePRNGBlocking avgt 25 95.634 ± 1.769 ms/op
SecureRandomPerformanceTest.measureTimePRNGNonBlocking avgt 25 95.797 ± 1.097 ms/op
我們發現,阻塞式和非阻塞式隨機數產生過程都耗時約 96 毫秒。阻塞式演算法和非阻塞式演算法之間的耗時差異非常小,在測量誤差範圍內。
6. 結論
本文探討了SecureRandom演算法如何利用 Linux 作業系統的隨機數字來源。我們重點討論了透過降低熵值來屏蔽隨機數源的可能性。然而,我們發現這種情況在任何現代 Linux 系統中都極不可能發生。
此外,我們還進行了一項簡單的對比測試,結果顯示阻塞演算法和非阻塞演算法的執行時間沒有顯著差異。
最後,我們發現非阻塞版本足以滿足所有實際應用需求。
和往常一樣,範例程式碼可在 GitHub 上找到。