crypto/rand 才是真安全,math/rand 完全不适用密钥场景;前者读取系统熵源不可预测,后者依赖可预测种子易被复现;正确调用应直接使用 crypto/rand.read 或 rand.int(rand.reader, max),避免手动字节转换;math/rand 仅适用于测试、游戏等非安全场景;性能实测开销极小,关键在合理批量读取。

crypto/rand 才是真安全,math/rand 完全不适用密钥场景
用 math/rand 生成密码、token、加密盐值,等于把门钥匙刻在门框上。它输出可预测,种子一旦泄露(比如用 time.Now().UnixNano()),整个序列都能复现。而 crypto/rand 读取操作系统提供的熵源(Linux 的 /dev/urandom,Windows 的 BCryptGenRandom),不可预测、无状态、无需手动 seed。
怎么正确调用 crypto/rand.Read?别自己封装字节转整数
常见错误是拿 crypto/rand.Read() 返回的字节切片,再用 binary.BigEndian.Uint64() 强转——这既不必要又容易出错(比如切片长度不够)。直接用标准库封装好的函数更稳:
- 生成随机
[]byte:直接调crypto/rand.Read(buf),确保buf已分配好长度 - 生成随机
int64或uint64:用rand.Int(rand.Reader, max)(注意是math/rand的Int函数,但第一个参数传crypto/rand.Reader) - 生成固定长度 token(如 32 字节 hex):用
hex.EncodeToString()包一层,别手写 base64 编码逻辑
math/rand 在哪些地方还能用?明确划清边界
math/rand 没被废弃,但它只适合非安全场景:单元测试里的模拟数据、游戏中的骰子、图形渲染的噪声。只要涉及「不能被猜中」或「不能被重现」,就必须换掉。特别注意这些坑:
- HTTP handler 里每次 new 一个
rand.New(rand.NewSource(time.Now().UnixNano()))—— 高并发下时间戳重复,大量 goroutine 共享相同 seed - 用
rand.Perm(n)洗牌用户 ID 列表后发给前端,以为“随机”就“不可推断”,实际只要知道时间点就能还原原始顺序 - 配置文件里写
seed: 42还沾沾自喜说“可复现”,这在安全上下文里是严重误用
性能差很多吗?实测其实没那么可怕
有人担心 crypto/rand 比 math/rand 慢十倍,不敢在高频路径用。真实情况是:单次 Read() 调用在现代 Linux 上平均耗时不到 1μs;批量生成(比如一次读 1KB)更是摊薄到忽略不计。真正瓶颈往往在后续的 base64/hex 编码、网络传输或数据库写入,而不是熵读取本身。唯一要注意的是:
立即学习“go语言免费学习笔记(深入)”;
- 别在 tight loop 里反复调
crypto/rand.Read()每次只读 1 字节——合并成大 buffer 一次性读 - 不要为每个小对象(如每个 HTTP 请求)都打开/关闭系统熵源——
crypto/rand.Reader是全局复用的 io.Reader,线程安全,直接用就行
安全随机数的“复杂点”不在 API 多难调,而在时刻分清「这里的数据会不会被攻击者观测或利用」。一旦模糊这个边界,换再好的库也白搭。










