PHP 中的隨機數——你覺得可靠麼?

OneAPM官方技術部落格發表於2015-12-22

本文主要分析以加密為目的的隨機數生成問題。PHP 5 並未提供生成強加密隨機數的簡便機制,但是,PHP 7 引入了兩個 CSPRNG 函式以解決該問題。系 OneAPM 工程師編譯整理。

PHP 中的隨機數——你覺得可靠麼?

什麼是 CSPRNG?

引用維基百科的定義,密碼安全的虛擬隨機數生成器(Cryptographically Secure Pseudorandom Number Generator,CSPRNG)是帶有特定屬性使之在密碼學中適用的虛擬隨機數生成器(pseudo-random number generator,PRNG)。

CSPRNG 主要用於:

  • 生成鍵(比如:生成複雜的鍵)
  • 為新的使用者賬號生成隨機密碼
  • 加密系統

保證高安全水準的一個重要因素便是高質量的隨機數。

PHP 7 中的 CSPRNG

PHP 7 為 CSPRNG 引入了兩種新函式:random_bytesrandom_int

random_bytes 函式返回 string 型別,並接受一個 int 型別為引數,該引數規定了所返回字串的位元組長度。

例如:

$bytes = random_bytes('10'); var_dump(bin2hex($bytes)); //possible ouput: string(20) "7dfab0af960d359388e6"

random_int 函式返回給定範圍內的整型數字。

舉例:

var_dump(random_int(1, 100)); //possible output: 27

幕後解密

以上函式的隨機數來源因環境不同而有所差異:

  • 在 Windows 系統,會使用 CryptGenRandom() 函式。
  • 在其他平臺,會優先使用 arc4random_buf() 函式(限 BSD 衍生系統或帶 libbsd 的系統)。
  • 若以上兩點均不符合,會使用 Linux [getrandom(2)](http://man7.org/linux/man-pages/man2/ getrandom.2.html) 系統呼叫。
  • 若以上來源均不符合,會丟擲 Error

一個簡例

一個好的隨機數生成系統能確保生成質量適合的隨機數。為了檢驗質量,需要執行一系列的統計試驗。此處,暫不深入討論複雜的統計話題,將已知的行為與隨機數生成器的結果進行比較,有助於質量評估。

一個簡單的測試方法是擲骰遊戲。假設投擲一次,投出6的概率是1/6。如果同時投擲三個骰子,投100次,投得零次、一次、兩次及三次6的次數大概是:

  • 0 次6 = 57.9 次
  • 1 次6 = 34.7 次
  • 2 次6 = 6.9 次
  • 3 次6 = 0.5 次

以下是骰子投擲100萬次的程式碼:

$times = 1000000; $result = []; for ($i=0; $i<$times; $i++){ $dieRoll = array(6 => 0); //initializes just the six counting to zero $dieRoll[roll()] += 1; //first die $dieRoll[roll()] += 1; //second die $dieRoll[roll()] += 1; //third die $result[$dieRoll[6]] += 1; //counts the sixes } function roll(){ return random_int(1,6); } var_dump($result);

用 PHP 7 的 random_int 與簡單的 rand 函式測試上面的程式碼,可能會得到:

Sixes expected random_int rand
0 579000 579430 578179
1 347000 346927 347620
2 69000 68985 69586
3 5000 4658 4615

更直觀地檢視 randrandom_int 的差別,可以運用方程式放大兩組結果的差異,並繪製成圖表:

php result - expected result / sqrt(expected)

得到的結果如下:

PHP 中的隨機數——你覺得可靠麼? (結果越接近零越好)

即便三個6的組合表現一般,且該測試與真實應用相比太過簡單,我們也能清楚地看到 random_int 的表現優於 rand。況且,隨機數生成器的可預見行為、重複行為越少,應用的安全程度就更高。

PHP 5 又如何呢?

預設情況下,PHP 5 並未提供任何強虛擬隨機數生成器。而實際使用中,可以使用 openssl_random_pseudo_bytes()mcrypt_create_iv() 方法,或直接結合使用 /dev/random/dev/urandomfread() 方法。此外,還有包 RandomLiblibsodium

如果你想用一個比較好的隨機數生成器,同時能與 PHP 7 相容,你可以使用 Paragon Initiative 公司的 random_compat 庫。該庫允許在 PHP 5.x 專案中使用 random_bytes()random_int() 方法。

該庫可以使用 Composer 進行安裝:

composer require paragonie/random_compat

require 'vendor/autoload.php'; $string = random_bytes(32); var_dump(bin2hex($string)); // string(64) "8757a27ce421b3b9363b7825104f8bc8cf27c4c3036573e5f0d4a91ad2aaec6f" $int = random_int(0,255); var_dump($int); // int(81)

random_compat 庫使用了與 PHP 7 中不同的優先序列:

  1. 如果可用,先使用 fread() /dev/urandom
  2. mcrypt_create_iv($bytes, MCRYPT_CREATE_IV)
  3. COM('CAPICOM.Utilities.1')->GetRandom()
  4. openssl_random_pseudo_bytes()

想了解為何採用這一優先序列,可以閱讀本文件

使用該庫生成密碼的簡單案例如下:

$passwordChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $passwordLength = 8; $max = strlen($passwordChar) - 1; $password = ''; for ($i = 0; $i < $passwordLength; ++$i) { $password .= $passwordChar[random_int(0, $max)]; } echo $password; //possible output: 7rgG8GHu

總結

你應該儘量使用在密碼學上安全的虛擬隨機數生成器。random_compat 庫為此提供了很好的實現方法。

如果你想使用可靠的隨機數來源,正如前文所述,儘快開始使用 random_intrandom_bytes 吧!

原文地址:http://www.sitepoint.com/randomness-php-feel-lucky/

OneAPM for PHP 能夠深入到所有 PHP 應用內部完成應用效能管理 能夠深入到所有 PHP 應用內部完成應用效能管理和監控,包括程式碼級別效能問題的可見性、效能瓶頸的快速識別與追溯、真實使用者體驗監控、伺服器監控和端到端的應用效能管理。想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格

相關文章