不能用 random 生成密码或 Token,因其基于伪随机数生成器(PRNG),种子可预测、输出可重现,缺乏密码学安全性;应改用 secrets 模块,如 token_urlsafe()、choice() 和 compare_digest() 等安全函数。

为什么不能用 random 生成密码或 Token
random 模块用的是伪随机数生成器(PRNG),种子可预测、输出可重现,不适合安全场景。比如用 random.choice() 生成的 API Token,如果攻击者知道程序启动时间或部分输出,就可能反推后续所有值。
真实踩坑案例:某内部工具用 random.SystemRandom().choice() 拼接 8 位字符串作登录 Token,结果被渗透测试人员通过进程内存快照还原出 PRNG 状态,批量生成有效 Token。
-
random不保证密码学安全性,哪怕调用了SystemRandom,其底层仍依赖 OS 的非加密随机源(如/dev/urandom)——但random自己没做熵校验和重采样 - Python 3.6+ 明确推荐用
secrets替代random做安全敏感操作 - Web 框架(如 Flask、FastAPI)的
secret_key或session_id生成逻辑,若手动拼接random结果,会直接降低整体安全性
用 secrets.token_urlsafe() 生成 URL 友好 Token
这是最常用也最省心的方式:生成 Base64Url 编码字符串,不含 +、/、=,能直接放进 URL、HTTP Header 或数据库字段。
注意长度参数是「字节长度」,不是最终字符串长度。Base64Url 编码后长度 ≈ ⌈(n × 4)/3⌉,所以要 32 字符 Token,传 24 字节更准:
立即学习“Python免费学习笔记(深入)”;
import secrets token = secrets.token_urlsafe(24) # 实际生成约 32 个字符 # 示例输出: 'xvL9aKmQ2rFzB4tN7wYpE8sJ'
- 默认使用
/dev/urandom(Linux/macOS)或CryptGenRandom(Windows),系统级密码学安全随机源 - 不建议用
secrets.token_urlsafe(16)生成密码——它只提供约 128 位熵,对长期口令不够;密码应至少 16 字节(128 bit)以上 - 避免在循环里反复调用生成多个 token 后拼接,这不会增加熵,反而可能引入时序侧信道
用 secrets.choice() + 字符集定制密码
需要控制字符类型(比如必须含大小写字母、数字、符号)时,用 secrets.choice() 安全地从自定义字符集中抽样。别用 random.choice(),哪怕加了 SystemRandom。
import secrets import string <p>alphabet = string.ascii<em>letters + string.digits + "!@#$%&*" password = ''.join(secrets.choice(alphabet) for </em> in range(16))
- 字符集别漏掉引号里的转义字符,比如
"\!@\#\$\%"写成"!@#$%"就行,Python 字符串本身不解释这些 - 不要用
secrets.randbelow(len(alphabet))再索引——虽然也安全,但比secrets.choice()多一次计算,且易写错边界(如randbelow(len(alphabet)-1)) - 如果要求“至少一个大写、一个小写、一个数字”,得用拒绝采样法重试,不能靠概率估算后固定位置插入——否则降低熵值
secrets.compare_digest() 防止时序攻击
生成完密码或 token 后,校验时别用 == 直接比较字符串。攻击者可通过响应时间差异推测出前缀是否匹配。
secrets.compare_digest() 是恒定时间比较函数,无论输入是否相等,执行时间都一致:
if secrets.compare_digest(user_input_token, stored_token):
grant_access()
else:
deny_access()- 仅适用于 bytes 或 str 类型,且两个参数类型必须一致(不能 str vs bytes)
- Django 和 Flask 的 session 签名验证底层已用类似机制,但你自己写的 token 校验点必须手动加
- 别试图自己实现恒定时间比较——容易写出被编译器优化掉的“假恒定时间”代码
真正难的不是选对函数,而是意识到哪些地方需要它:Token 生成只是起点,存储加密、传输 HTTPS、校验方式、过期策略、错误提示粒度……每个环节松动一环,前面用 secrets 做的就白费。


















