隨機數生成是指產生不可預測數字的過程,在許多依賴隨機性的不確定性應用中發揮着關鍵作用。一個理想的隨機數序列不應具備可預測性。
“真正”的隨機數生成可以依賴多種方式,例如擲硬幣、擲骰子,甚至基於自然現象的物理過程,如宇宙輻射、大氣壓力或熔岩燈的變化等,這些方法在計算機中極難被模擬或預測。
為此,大多數現代操作系統都嘗試提供接近真實隨機性的生成器,通常基於硬盤旋轉延遲等系統層面的測量機制。為了提升安全性,系統會定期重置隨機數生成器的種子,從而防止因休眠、內存訪問等事件泄露內部狀態或被繞過安全保護。
在實際應用中,操作系統與編程語言的運行時環境通常內置隨機數生成器(RNG),通過特定算法結合物理熵源生成偽隨機數。由於偽隨機數生成器依賴一個初始“種子”,一旦種子值與算法被掌握,整個隨機序列都可能被預測。因此,在安全或對隨機性要求較高的場景中,選擇合適的隨機數生成器至關重要。
PHP 中的隨機數生成器
PHP 提供了幾種生成隨機值的方法:rand、mt_rand、random_int、random_bytes、openssl_random_pseudo_bytes 等,其中一些是偽隨機數生成器,但它們帶有各種屬性,使它們彼此不同,並且必須優先於另一個。
隨機數生成器無處不在,從簡單的電腦遊戲到 PIN 碼,再到使用隨機生成的加密密鑰加密的敏感信息。在大多數應用程序中,使用適當的隨機數生成器至關重要,以確保惡意行為者無法確定系統內生成的隨機數。
所有偽隨機生成器本質上都依賴於一個 “種子” 值,該值確定使用給定算法生成的所有整個隨機數序列。計算機遊戲(如 Minecraft)基於單個種子值構建整個遊戲世界。為了構建一個相同的遊戲世界,只需要初始種子。
PHP 中可用的隨機數生成器在實現方式以及它們在依賴生成器的隨機性來確保安全性的應用程序中使用的安全性方面有很大差異。
從 PHP 8.2 開始,PHP 提供了以下隨機數生成器:
| 函數/類 | 可用性 | 筆記 |
|---|---|---|
rand |
PHP 4 及更高版本 | 不安全,不適合任何與安全相關的應用程序。自 PHP 7.1 起為 mt_rand 的別名 |
mt_rand |
PHP 4 及更高版本 | 使用 Mersenne Twister,在 PHP 7.1 中有所改進,但本質上仍不安全 |
lcg_value |
PHP 4 及更高版本 | 返回 0 到 1 之間的浮點數(包括 0 和 1),加密不安全 |
random_int |
PHP 7.0 及更高版本 | 推薦使用,因為它在加密上是安全的 |
random_bytes |
PHP 7.0 及更高版本 | 推薦使用,如果系統熵不足會失敗,不會回退到不安全算法 |
openssl_random_pseudo_bytes |
OpenSSL 和 PHP 5.3 及更高版本 | 可用於獲取加密安全的隨機數,但不能保證,因為該函數可能會回退到不安全的算法 |
Random\Engine\Mt19937 |
PHP 8.2 及更高版本 | mt_rand 的面向對象接口 |
Random\Engine\PcgOneseq128XslRr64 |
PHP 8.2 及更高版本 | 置換同餘生成器實現 |
Random\Engine\Xoshiro256StarStar |
PHP 8.2 及更高版本 | Xoshiro PRNG(偽隨機數生成器)實現 |
測量 PHP PRNGs 中的隨機性
測量隨機數生成器隨機性的最簡單方法之一是可視化產生的值以觀察模式。
使用 PHP 的 GD 擴展,可以通過在隨機的 X 和 Y 座標上放置單個像素來繪製圖像。在預先確定的嘗試次數上,像素分佈大致均勻的圖像表示隨機數生成器接近“真實”RNG。如果種子保持不變,則純粹基於種子值生成值的 RNG(例如 Mersenne Twister)應生成相同的輸出。
// 將測試函數命名為輸出圖像文件的名稱
$test_name = 'random_int';
$size = 250;
$random_function = static function(int $max) {
return random_int(0, $max);
};
// 創建一個指定 $size 的空白正方形圖像
$im = imagecreatetruecolor($size, $size);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// 將圖像填充為白色
imagefill($im, 0, 0, $white);
$iterations = $size ** 2;
for ($i = 0; $i < $iterations; $i++) {
$x = $random_function($size);
$y = $random_function($size);
imagesetpixel($im, $x, $y, $black);
}
imagepng($im, $test_name . '.png', 0);
imagedestroy($im);
random_int 函數是 CSPRNG(加密安全偽隨機數生成器),自 PHP 7.0 起可用。如果它無法產生具有足夠隨機性的數字的衰老,則會引發 Exception,並且不會回退到任何不安全的 RNG 源。
此外,無法更改此 RNG 的種子值,這是在 PHP 中獲取隨機數和字節的推薦方法,即使對於加密作也是如此。
random_int / random_bytes
$random_function = static function(int $max) {
return random_int(0, $max);
};
使用上面的代碼段來可視化隨機性,random_int 函數會生成類似於以下內容的圖像:
“random_int”函數隨機性的可視化,顯示從 RNG 返回的均勻分佈值
PHP 8.2 的新功能: 在 PHP 8.2 中,還可以使用Random\Engine\Secure引擎將random_int和random_bytes PRNG與scoped和OOP API一起使用。PHP 8.2 中有更詳細的示例:新的隨機擴展 - 使用示例
mt_rand 函數和 Random\Engine\Mt19937 引擎
Mersenne Twister 是 PHP 中可用的偽隨機數生成器。它的 seed 值是可配置的,但 PHP 在開始時會用足夠隨機的種子來設定它的種子。
$random_function = static function(int $max) {
return mt_rand(0, $max);
};
“mt_rand”函數隨機性的可視化,顯示從 RNG 返回的均勻分佈值
Mersenne Twister RNG 與混合/硬件方法之間的一個主要區別是隨機數的整個序列都取決於種子。PHP 的 mt_srand 用於為 RNG 設定種子。Mersenne Twister 為同一種子生成相同的隨機數序列。
當應用程序/遊戲需要基於單個種子(例如 Minecraft)生成值時,這種確定性性質非常有用。但是,對於需要生成隨機數以進行加密的應用程序來説,Mersenne Twister 是一個糟糕的選擇,因為 RNG 的內部狀態可以通過觀察 RNG 產生的隨機數序列來推導出來。
為了可視化這一點,使用 mt_rand() 函數生成以下三個。其中兩個使用 $seed = 42,最後一個使用 $seed = 43:
mt_srand($seed);
$random_function = static function(int $max) {
return mt_rand(0, $max);
};
為了更好地可視化相似之處和不同之處,使用 imagefilledrectangle() 函數生成了以下圖像,以繪製填充矩形而不是單個像素。矩形的顏色也是隨機選擇的。
| 種子值示例 | 説明 | 圖示 |
|---|---|---|
$seed = 42(第一次運行) |
只要種子值相同,生成的隨機數序列就保持不變。 | |
$seed = 42(第二次運行) |
只要種子值相同,生成的隨機數序列就保持不變。 | |
$seed = 43 |
即使更改種子值一個字節也會產生完全不同的隨機數系列。 |
rand 功能
從 PHP 7.1 開始,PHP 的 rand 函數在後台使用 MT,結果與 mt_rand 函數相同。然而,在 PHP 7.0 之前,rand 函數產生了更可預測和重複的隨機數流,這使得它與真正的隨機數生成器相去甚遠。
$random_function = static function(int $max) {
return rand(0, $max);
};
可視化 'rand' 函數,具有可見的可預測模式
在 PHP 7.1 及更高版本中使用 rand 必然是壞的,因為它無論如何都會使用 mt_rand。但是,要確保靜態分析器不會標記 rand 的使用,請考慮遷移到 random_int。
lcg_value 功能
lcg_value() 是一個組合線性同餘生成器 ,它返回一個介於 0 和 1 之間的偽隨機數。此函數也不被視為加密安全的偽隨機數生成器。
$random_function = static function(int $max) {
return abs((int) (lcg_value() * $max));
};
可視化“lcg_value”功能,具有看似隨機的噪聲且沒有可見的模式
Random\Engine\PcgOneseq128XslRr64 和 Random\Engine\Xoshiro256StarStar
在 PHP 8.2 中,大多數 RNG 函數被移動到一個名為 random 的新擴展中。它引入了一個新的 \Random\Randomizer 類,以及四個作為 PHP 類的 RNG 引擎。
使用新的隨機擴展添加的兩個新 PRNG 是 PcgOneseq128XslRr64(置換同餘生成器實現)和 Xoshiro256StarStar(Xoshiro PRNG 實現)。它們都類似於 Mersenne Twister,因為它們都只依賴於初始種子值,只要種子值保持不變,就可以用作確定性數字流。
| PRNG 算法 | $seed = 42(第一次運行) | $seed = 42(第二次運行) | $seed = 43 |
|---|---|---|---|
| 可視化説明 | 只要種子值相同,生成的隨機數序列就保持不變。 | 只要種子值相同,生成的隨機數序列就保持不變。 | 即使更改種子值一個字節也會產生完全不同的隨機數系列。 |
| PcgOneseq128XslRr64 | |||
| Xoshiro256StarStar |
PHP 提供了幾個隨機數生成器,選擇正確的隨機數生成器很重要,尤其是當涉及到隨機數流的不可預測性很重要的應用程序時。一些 RNG,比如 PHP 7.1 之前的 rand() 本質上是不安全和可預測的。
PHP 8.2 除了現有的 Mersenne Twister(帶有帶有 Mt19937 的 OOP API)之外,還提供 Xoshiro256StarStar 和 PcgOneseq128XslRr64 作為兩個額外的 RNG。
並非所有 RNG 都適用於加密安全偽隨機數生成器 (CSPRNG)。在 PHP 7.0 之前,OpenSSL 擴展的 openssl_random_pseudo_bytes 函數是更好的選擇,但即使這樣也不理想,因為如果不可能隨機數具有足夠的熵,它通常會返回 false。此問題後來在 PHP 7.4 中得到修復,在 RNG 失敗時拋出異常。
Mersenne Twister、Xoshiro256StarStar 和 PcgOneseq128XslRr64 等 RNG 也有用例,但由於它們依賴於單個種子值,因此不建議將它們用於任何需要 CSPRNG 的應用程序。
對於所有隨機數生成目的,建議使用random_int和random_bytes函數,因為它會失敗並顯示異常(而不是靜默默認不安全的算法),並且自 PHP 7.0 以來的所有 PHP 版本都支持它。
不使用 RNG 進行加密使用的應用程序可以使用更快的實現,例如 Xoshiro256StarStar。