使用 random 模块生成密码或 token 是安全漏洞,因其基于可预测的 mersenne twister 算法;应改用 secrets 模块,如 secrets.token_urlsafe() 或 secrets.token_bytes()。

用 random 生成密码或 token 就是漏洞
Python 标准库的 random 模块不是密码学安全的——它用的是 Mersenne Twister 算法,可被预测。只要知道几个输出值,就能反推出内部状态,进而预测后续所有“随机”结果。
常见错误现象:random.choice() 选验证码、random.randint() 生成 API key、random.shuffle() 洗牌敏感数据。
- 使用场景:生成 session id、重置令牌、加密盐值、一次性密码(TOTP 种子)
- 正确做法:一律换用
secrets模块,它是专为安全场景设计的 - 示例对比:
random.SystemRandom().randint(1, 100)虽调用系统熵源,但接口仍绕不开random的抽象层,不推荐;直接用secrets.randbelow(100) + 1更稳妥
secrets.token_urlsafe() 和 secrets.token_hex() 怎么选
两者都基于操作系统提供的加密安全随机数生成器(如 Linux 的 /dev/urandom),但编码方式和适用场景不同。
-
token_urlsafe()返回 Base64Url 编码字符串(不含+、/、=),适合放在 URL、HTTP header、文件名里 -
token_hex()返回纯十六进制字符串,长度是字节数的两倍(如secrets.token_hex(16)→ 32 字符),适合存数据库或做对称密钥材料 - 性能差异极小,别纠结;但注意:不要用
secrets.token_urlsafe(16)当 AES 密钥——Base64Url 解码后只有 ~12 字节,不够 16/32 字节要求
在加密流程中混用 random 和 secrets 会出事
哪怕只有一处用了 random,整个链路的安全性就归零。典型误用是“主逻辑用 secrets,但 IV 或 nonce 用 random.getrandbits()”。
立即学习“Python免费学习笔记(深入)”;
- IV/nonce 必须不可预测且唯一,
random.getrandbits(128)输出可被还原,攻击者能复现加密流 - Fernet、cryptography 库的某些高阶封装(如
Fernet.generate_key())内部已用secrets,但你自己手写 AES-GCM 时,os.urandom()或secrets.token_bytes()才是唯一选择 - 兼容性提醒:Python secrets 模块,必须降级用
os.urandom(),别 fallback 到random
测试环境里还用 secrets?小心 CI 失败
CI 环境(尤其是某些容器化 runner)可能限制对熵池的访问,导致 secrets 抛 OSError: [Errno 24] Too many open files 或卡住。
- 这不是代码 bug,是环境配置问题;但更常见的坑是:开发者为让测试过掉,偷偷把生产用的
secrets.token_urlsafe()替换成random.choices('abc123', k=16) - 正确解法:测试中保留
secrets调用,但用 pytest 的 fixture 或 unittest.mock.patch 替换其底层熵源(mockos.urandom),而非替换模块行为本身 - 容易被忽略的一点:Docker 默认限制
/dev/random,但/dev/urandom是 OK 的;如果真遇到阻塞,先查是否误配了RANDOM_DEVICE环境变量或挂载了只读/dev










