PHP随机字符串生成器究竟是如何工作的?深入内部一探究竟
您是否曾想过,当您在网站上点击“生成密码”时,背后发生了什么?或者当系统为您的会话创建唯一标识符时呢?十有八九,背后都是PHP随机字符串生成器在起作用。看起来很简单:取一个字符数组,打乱,输出结果。但魔鬼,一如既往,藏在细节之中。
在Stack Overflow上,“PHP random string generator”这个问题获得了数千次浏览。这并非偶然。新手用rand()编写简陋方案,有经验的开发者使用random_int(),而专家们则在争论熵的问题。让我们来弄清楚这一切究竟是如何运作的,并编写我们自己的理想生成器。
为什么rand()是邪恶的?(剧透:可预测性)
最流行的“新手”方法是在循环中使用rand():
<?phpfunction generateRandomString($length = 10) { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); $randomString = ''; for ($i = 0; $i < $length; $i++) { $randomString .= $characters[rand(0, $charactersLength - 1)]; } return $randomString;}echo generateRandomString(); // 输出: "aB3kL9xQ2m"?>这段代码能运行。但前提是您不在乎安全性。rand()函数使用线性同余生成器(LCG)。这是一个确定性算法:知道当前状态,就可以预测所有后续数字。此外,如果您没有调用srand(),rand()默认初始化为1。而如果您调用了,种子通常来自time(),这仅提供2-3百万种可能性。暴力破解只需几秒钟。
结论:rand()适用于游戏和随机选择头像,但绝对不适合生成令牌、密码或API密钥。
认识mt_rand()——更快,但未必更安全
PHP 4引入了mt_rand(),它基于梅森旋转算法(Mersenne Twister)。它更快,周期更长(2^19937对比rand的2^31)。许多人至今仍将其作为“高级”版本使用:
<?phpfunction generateTokenMtRand($length = 32) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $token = ''; for ($i = 0; $i < $length; $i++) { $token .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $token;}?>但这里也有陷阱。梅森旋转算法是密码学上不安全的。通过624个生成的数字序列,可以恢复生成器的内部状态并预测所有未来值。对于攻击密码重置令牌来说,这已经绰绰有余。
寓意:mt_rand()是模拟和测试的绝佳选择,但不适用于安全性。
神圣三位一体:random_int()、CSPRNG和熵
从PHP 7.0开始,我们有了random_int()和random_bytes()。它们是密码学安全的伪随机数生成器(CSPRNG)。这在实践中意味着什么?
- 熵:PHP从系统中收集“噪声”:按键间隔时间、鼠标移动、网络数据包、磁盘中断。在Linux上使用
/dev/urandom,在Windows上使用CryptGenRandom。 - 不可预测性:即使知道算法,也无法恢复种子或预测下一个数字。
- 均匀分布:每个字节出现的概率恰好是1/256。
以下是正确的PHP随机字符串生成器的样子