编程

PHP 随机数函数的随机性测试

804 2024-04-18 18:00:15

随机数生成是一个生成无法合理预测的数字的过程。数字序列不应该是可预测的,它在依赖随机数序列的不可预测性的应用程序中发挥着重要作用。

“真”随机数生成过程可以包括任何东西,从简单的掷硬币、掷骰子到宇宙辐射测量、大气压、熔岩灯,以及其他取决于几个自然发生的物理方面的物理手段,这使得在计算机上进行预测变得相当困难。

大多数现代计算机操作系统试图提供一个接近的“真实”随机数生成器,利用诸如旋转硬盘延迟之类的测量来模拟“真实”的随机数生成器的接近替代方案。它们的实现细节也使它们更加安全。例如,操作系统定期为随机数生成器重新设定种子,以防止在休眠事件或其他可能绕过内核内存保护访问内存的事件中影响随机数生成器的潜在泄漏内部状态。

在现实生活中的计算机应用程序中,操作系统和编程语言运行时提供了随机数生成器(RNG),该生成器使用各种算法来生成随机数,并利用可用的物理随机性来增加生成器的随机性。这些伪随机数生成器的一个基本缺点是,它们依赖于初始“种子”值,并且知道初始种子值和算法就足以预测整个随机数流,从而使使用适当的随机数生成器对任何应用都至关重要。

PHP 中的随机数生成器

PHP 提供了几种生成随机值的方法:randmt_randrandom_intrandom_bytesopenssl_random_pseudo_bytes 等。其中一些是伪随机数生成器,但它们具有各种不同的属性,必须优先于其他生成器。

随机数生成器广泛使用,从简单的计算机游戏到 PIN 号码,再到使用随机生成的加密密钥加密的敏感信息。在大多数应用中,使用合适的随机数生成器至关重要,以确保恶意行为者无法确定系统内生成的随机数。

所有伪随机生成器本质上都依赖于一个“种子”值,该值决定了使用给定算法生成的所有随机数序列。《我的世界》等电脑游戏基于一个种子值构建了整个游戏世界。为了建立一个完全相同的游戏世界,所需要的只是最初的种子。

PHP 中可用的随机数生成器在实现方式以及在依赖生成器随机性的应用程序中使用的安全性方面有很大差异。

从 PHP 8.2 开始,PHP 提供了以下随机数生成器:

Function/ClassAvailabilityNotes
randPHP 4 and later不安全,不适合任何与安全相关的应用程序。自 PHP 7.1 起别名为 mt_rand
mt_randPHP 4 and later使用 Mersenne Twister。在 PHP 7.1 中收到了一些错误修复和改进,但本质上仍然不安全。
lcg_valuePHP 4 and later返回一个包含 0 和 1 的随机浮点值。非加密安全。
random_intPHP 7.0 and later推荐,因为它在加密方面是安全的
random_bytesPHP 7.0 and later推荐使用,因为它在加密方面是安全的。如果系统不能生成加密安全的随机数,那么它就会失败,而不会退回到不安全的算法。
openssl_random_pseudo_bytesOpenSSL and PHP 5.3 and later可以用于获取加密安全的随机数,但不能保证,因为该函数可能会回退到不安全的算法。
Random\Engine\Mt19937PHP 8.2 and latermt_rand 的面向对象接口
Random\Engine\PcgOneseq128XslRr64PHP 8.2 and later置换同余生成器实现。
Random\Engine\Xoshiro256StarStarPHP 8.2 and laterXoshiro PRNG 实现

PHP PRNG 中随机性的测量

测量随机数生成器随机性的最简单方法之一是将生成的值可视化以观察模式。

使用 PHP 的 GD 扩展,可以通过将随机的 X 和 Y 坐标上放置一个像素来绘制图像。在预定次数的尝试中,像素分布基本均匀的图像表示一个接近“真实” RNG 的随机数生成器。如果种子保持不变,则纯粹基于种子值产生值的 RNG(如 Mersenne Twister)应产生相同的输出。

// Name the testing function, which is used as the output image file name.
$test_name = 'random_int';
$size = 250;

$random_function = static function(int $max) {
    return random_int(0, $max);
};

// Create an empty square image with a given $size.
$im = imagecreatetruecolor($size, $size);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// Fill the image in white
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 / random_bytes

random_int 函数是自 PHP 7.0 以来可用的 CSPRNG(加密安全伪随机数生成器)。如果它不能以足够的随机性产生数字的衰老,并且不会回退到任何不安全的 RNG 源,那么它就会抛出异常。

此外,不可能更改此 RNG 的种子值,这是在 PHP 中获得随机数和字节的推荐方法,即使是加密操作也是如此。

$random_function = static function(int $max) {
    return random_int(0, $max);
};

使用上面的片段来可视化随机性,random_int 函数生成一个类似于以下的图像:

random_int 随机性的可视化,显示从 RNG 返回的等分布值

PHP 8.2 中新增
在 PHP 8.2 中,还可以使用 random_intrandom_bytes PRNG 以及使用 Random\Engine\Secure 引擎的作用域和 OOP API。PHP 8.2 中提供了更详细的示例:新随机扩展-用法示例

mt_rand 函数及 Random\Engine\Mt19937 引擎

Mersenne Twister 是 PHP 中可用的伪随机数生成器。它的种子值是可配置的,不过 PHP 在一开始就用一个足够随机的种子对其进行播种。

$random_function = static function(int $max) {
    return mt_rand(0, $max);
};
mt_rand函数随机性的可视化,显示从 RNG 返回的等分布值

 

Mersenne Twister RNG 和混合/硬件方法之间的一个主要区别是,随机数的整个序列取决于种子。PHP 的 mt_srand 用于 RNG 的播种。Mersenne Twister 为相同的种子生成相同的随机数序列。

当应用/游戏需要基于单个种子(如《我的世界》)生成值时,这种确定性很有用。然而,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
只要种子值相同,生成的随机数序列就保持不变。只要种子值相同,生成的随机数序列就保持不变。将种子值更改一个字节也会产生完全不同的随机数序列。
mt_rand run with seed = 42, round 1mt_rand run with seed = 42, round 2mt_rand run with seed = 43, round 1

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
可视化只要种子值相同,生成的随机数序列就保持不变。只要种子值相同,生成的随机数序列就保持不变。将种子值更改一个字节也会产生完全不同的随机数序列。
PcgOneseq128XslRr64PcgOneseq128XslRr64 run with seed = 42, round 1PcgOneseq128XslRr64 run with seed = 42, round 2PcgOneseq128XslRr64 run with seed = 43
Xoshiro256StarStarPcgOneseq128XslRr64 run with seed = 42, round 1PcgOneseq128XslRr64 run with seed = 42, round 2PcgOneseq128XslRr64 run with seed = 43

PHP 提供了几个随机数生成器,选择正确的随机数生成器很重要,尤其是当涉及到随机数流的不可预测性很重要的应用程序时。一些 RNG,如 PHP 7.1 之前的 rand(),本质上是不安全和可预测的。

PHP 8.2 提供 Xoshiro256StarStarPcgOneseq128XslRr64 作为除了现有 Mersenne Twister 之外的两个附加 RNG(带有 Mt19937 的 OOP API)。

并非所有 RNG 都适用于加密安全伪随机数生成器(CSPRNG)。

在 PHP 7.0 之前,来自 OpenSSL 扩展的 openssl_random_pseudo_bytes 函数是一个更好的选择,但即使这样也不理想,因为如果不可能使用具有足够熵的随机数,它也会返回 false。这后来在 PHP 7.4 中被修复,以便在 RNG 失败时抛出异常。

Mersenne Twister、Xoshiro256StarStar 和 PcgOneseq128XslRr64 等 RNG 都有用例,但由于它们依赖于单个种子值,因此不建议将它们用于任何需要 CSPRNG 的应用程序。

对于所有随机数生成,建议使用 random_intrandom_bytes 函数,因为它会因异常而失败(而不是默认不安全的算法),并且自 PHP 7.0 以来的所有 PHP 版本都支持它

不使用 RNG 进行加密的应用可以使用更快的实现,如 Xoshiro256StarStar

 

PHP