先分清两类随机数
PHP 里的随机数函数看似都差不多,实际分成两条路线:伪随机(rand、mtrand)和密码学安全随机(randomint、random_bytes)。前者追求速度,后者追求不可预测。选错函数的代价,往往在安全场景里才暴露。
rand 与 mt_rand:日常够用,别碰安全
rand() 是历史遗留函数,底层实现在不同 PHP 版本间有过变化,行为不够稳定。mtrand() 基于 Mersenne Twister 算法,速度更快、分布也更均匀,在 PHP 7.1 之后 rand() 已被映射到 mtrand(),但直接写 mt_rand() 语义更清晰。
典型用途:生成测试数据、随机排序、简单的展示型随机内容。
$n = mt_rand(1, 100); // 1 到 100 的整数
$arr = [1, 2, 3];
shuffle($arr); // 内部同样依赖伪随机
关键限制:Mersenne Twister 的状态一旦被观察到足够多的输出,理论上可以推算后续序列。所以抽奖、发券、验证码、会话令牌这类涉及利益或安全的场景,不要用 mt_rand。
random_int:安全场景的默认选择
random_int($min, $max) 使用操作系统的 CSPRNG(如 Linux 的 getrandom),结果不可预测,失败时抛出异常而不是静默返回 false。
try {
$code = random_int(100000, 999999); // 六位验证码
$token = bin2hex(random_bytes(16)); // 32 位十六进制令牌
} catch (Exception $e) {
// 熵源不可用,应中断流程而非降级
}
注意异常处理不是可选项:熵源枯竭或系统限制时,继续执行反而更危险。
几个容易踩的坑
用取模制造偏差
randomint(0, PHPINTMAX) % 10 这类写法会引入模偏差——当区间长度不能被 10 整除时,部分数字出现概率略高。正确做法是直接 randomint(0, 9),让函数内部处理拒绝采样。
播种不当
mtsrand(time()) 是典型反模式:时间可猜,等于把随机性交了出去。PHP 7.1 起 mtrand 会自动播种,手动播种基本没有必要,除非要做可复现的测试。
把随机当唯一
随机数生成成功不代表业务安全。唯一性约束、幂等校验、频率限制仍需在数据库和业务层补齐,随机只是其中一环。
选型速查
- 展示、排序、测试数据:
mt_rand - 验证码、令牌、抽奖、密码盐:
randomint/randombytes - 需要可复现的随机序列:显式
mt_srand,仅限测试环境 - 需要浮点随机:
randomint生成整数后除以精度,别用mtrand()/mt_getrandmax()做安全用途
一句话原则:凡是结果可能被攻击者利用,就用 randomint;其余场景 mtrand 足够。