商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python如何安全地生成随机密码或Token_使用secrets模块替代random

Python如何安全地生成随机密码或Token_使用secrets模块替代random

  发布于2026-07-06 阅读(0)

扫一扫,手机访问

在安全领域,生成密码或 Token 时,`random` 模块是绝对不能用的。因为 `random` 依赖的是伪随机数生成器(PRNG),种子可预测、输出可重现,压根不具备密码学安全性。而 `secrets` 模块才是正确选择,它提供了 `token_urlsafe()`、`choice()` 和 `compare_digest()` 等安全函数。 先来看一个真实案例:某内部工具用 `random.SystemRandom().choice()` 拼接了一个 8 位字符串作为登录 Token,结果被渗透测试人员通过进程内存快照还原出 PRNG 状态,直接批量生成了有效 Token。这可不是危言耸听,而是真实踩过的坑。 ---

为什么不能用 random 生成密码或 Token

`random` 模块用的是伪随机数生成器(PRNG),种子可预测、输出可重现,不适合安全场景。比如用 `random.choice()` 生成的 API 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` 也不行。 ```python import secrets import string alphabet = string.ascii_letters + string.digits + "!@#$%&*" password = ''.join(secrets.choice(alphabet) for _ in range(16)) ``` 几个容易忽略的细节: - 字符集别漏掉引号里的转义字符,比如 `\!@\#$\%` 直接写成 `!@#$%` 就行,Python 字符串本身不解释这些。 - 不要用 `secrets.randbelow(len(alphabet))` 再索引——虽然也安全,但比 `secrets.choice()` 多一次计算,且易写错边界(如 `randbelow(len(alphabet)-1)`)。 - 如果要求“至少一个大写、一个小写、一个数字”,得用拒绝采样法重试,不能靠概率估算后固定位置插入——否则会降低熵值。

secrets.compare_digest() 防止时序攻击

生成完密码或 token 后,校验时别用 `==` 直接比较字符串。攻击者可通过响应时间差异推测出前缀是否匹配。 `secrets.compare_digest()` 是恒定时间比较函数,无论输入是否相等,执行时间都一致: ```python 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` 做的就白费了。

Python如何安全地生成随机密码或Token_使用secrets模块替代random

本文转载于:https://www.php.cn/faq/2450362.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注