Python 随机数在安全场景下的误用

2次阅读

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

Python 随机数在安全场景下的误用

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 字节要求

在加密流程中混用 randomsecrets 会出事

哪怕只有一处用了 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)可能限制对熵池的访问,导致 secretsOSError: [errno 24] Too many open files 或卡住。

  • 这不是代码 bug,是环境配置问题;但更常见的坑是:开发者为让测试过掉,偷偷把生产用的 secrets.token_urlsafe() 替换成 random.choices('abc123', k=16)
  • 正确解法:测试中保留 secrets 调用,但用 pytest 的 fixture 或 unittest.mock.patch 替换其底层熵源(mock os.urandom),而非替换模块行为本身
  • 容易被忽略的一点:docker 默认限制 /dev/random,但 /dev/urandom 是 OK 的;如果真遇到阻塞,先查是否误配了 RANDOM_DEVICE 环境变量或挂载了只读 /dev

事情说清了就结束。真正难的不是记住该用哪个函数,而是每次写到“随机”二字时,下意识停半秒:这地方崩了,用户密码会不会被批量猜出来。

text=ZqhQzanResources