随机数生成是指产生不可预测数字的过程,在许多依赖随机性的不确定性应用中发挥着关键作用。一个理想的随机数序列不应具备可预测性。
“真正”的随机数生成可以依赖多种方式,例如掷硬币、掷骰子,甚至基于自然现象的物理过程,如宇宙辐射、大气压力或熔岩灯的变化等,这些方法在计算机中极难被模拟或预测。
为此,大多数现代操作系统都尝试提供接近真实随机性的生成器,通常基于硬盘旋转延迟等系统层面的测量机制。为了提升安全性,系统会定期重置随机数生成器的种子,从而防止因休眠、内存访问等事件泄露内部状态或被绕过安全保护。
在实际应用中,操作系统与编程语言的运行时环境通常内置随机数生成器(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。