rand()和mt_rand()均非加密安全,因其基于可预测的确定性算法(LCG或Mersenne Twister),易被反推种子并预测全部输出,故严禁用于会话ID、令牌等安全场景;应改用random_int()或PHP 8.2+的Randomizer::getInt()。

rand() 不是加密安全的,它用的是线性同余生成器(LCG),输出可预测、周期短、状态易被还原。哪怕只调用一次,攻击者在知道几个输出后就能反推种子并预测后续所有值——这在会话 ID、密码重置令牌、API 密钥生成等场景里等于直接开门。
rand() 和 mt_rand() 为什么不能用于安全场景
它们共享同一个全局随机状态,且都基于确定性算法:
-
rand()是 LCG 实现,mt_rand()是 Mersenne Twister(mt19937),虽然后者周期长得多(2¹⁹⁹³⁷−1),但仍是伪随机,不抗状态恢复 - 任意地方调用
mt_srand()或srand()都会污染全局状态,影响其他模块的随机行为(比如某个日志组件悄悄重置了种子,你的 token 生成就变可预测) - 没有 CSPRNG(Cryptographically Secure Pseudo-Random Number Generator)保障,PHP 文档明确标注“不可用于加密或要求不可猜测的用途”
Randomizer::getInt() 和 random_int() 的关键区别
二者都安全,但设计目标和使用方式不同:
-
random_int()是函数式接口,底层直连系统 CSPRNG(/dev/urandom或CryptGenRandom),无状态、无依赖,适合简单整数生成 -
Randomizer是面向对象封装,允许你显式选择Random\Engine,比如用Random\Engine\Secure(默认)或自定义引擎;还支持浮点、字节、数组打乱等高级操作 -
Randomizer实例可复用、可注入、可 mock,更适合测试和 DI 场景;而random_int()每次都是独立调用 - 注意:
Randomizer::getInt()在 PHP 8.2+ 才可用,random_int()自 PHP 7.0 起就存在,兼容性更广
迁移 rand() 到 Randomizer 的实操要点
不是简单替换函数名,得注意语义和边界:
立即学习“PHP免费学习笔记(深入)”;
- 原
rand(1, 100)→ 改为(new Random\Randomizer())->getInt(1, 100);注意getInt()的上下界都是包含的([min, max]),和rand()一致 - 如果之前用
rand()生成字符串(比如拼接字母表),别手写循环,改用$r->getBytesFromString('abcdefghijklmnopqrstuvwxyz', 8) - 避免重复创建
Randomizer实例——它内部已做优化,但高频调用(如每请求上百次)建议复用单例或容器注入 - 不要试图用
Randomizer模拟可重现序列:它默认用Secure引擎,不支持种子;如真需可重现(仅限测试),得手动传入Random\Engine\PcgOneseq128XslRr64等非安全引擎
容易被忽略的兼容性陷阱
PHP 8.2 是分水岭,但很多项目还在 8.1 或更低版本跑:
-
Random\Randomizer类仅存在于 PHP ≥ 8.2.0;低于此版本会Fatal error: Uncaught Error: Class "Random\Randomizer" not found - 不能靠
function_exists('Random\Randomizer')检测,得用class_exists('Random\Randomizer') - 若需跨版本兼容,建议封装一层适配器:PHP 8.2+ 用
Randomizer,旧版回退到random_int()+random_bytes()组合,而不是降级到mt_rand() - Composer autoload 不会自动加载
Random命名空间,无需额外配置,但 IDE 可能报未识别类——加@phpstan-ignore-next-line或升级 PHPStan 插件即可
真正麻烦的不是换函数,而是清理所有隐式依赖全局随机状态的地方:比如某个第三方库悄悄调用了 mt_srand(),或者你在 config.php 里写了 srand(123) 来“调试方便”。这些残留才是上线后随机性突然失效的根源。



















