应使用 bcrypt 而非 hashlib 手动加盐,因其自动处理随机盐、迭代轮数、恒定时间比较;注册用 hashpw()+gensalt(rounds=12),登录用 checkpw() 校验 bytes 类型密码与哈希。

用 bcrypt 而不是 hashlib 手动加盐
Flask 本身不提供密码哈希工具,直接用 hashlib.sha256() 或 hashlib.pbkdf2_hmac() 容易漏掉关键步骤:比如盐值生成、迭代轮数控制、验证时恒定时间比较。这些细节一旦出错,就可能引发时序攻击或彩虹表破解。
正确做法是用成熟封装库:bcrypt(推荐)或 passlib。它们自动处理盐值随机性、算法版本标识、以及安全的验证逻辑。
-
pip install bcrypt(纯 Python 实现,无需编译) - 注册新用户时调用
bcrypt.hashpw(),传入明文密码和bcrypt.gensalt(rounds=12) -
rounds=12是当前较稳妥的迭代强度(默认 12,值越大越慢但越抗暴力) - 哈希结果是字节串,存进数据库前需解码为
utf-8字符串(如hashed.decode('utf-8'))
登录时必须用 bcrypt.checkpw() 校验,别自己比对字符串
常见错误是把数据库里取回的哈希值和 hashpw() 新算的结果用 == 比较——这会暴露时序信息。而 bcrypt.checkpw() 内部使用恒定时间比较,且能自动提取原哈希里的盐和参数。
校验代码要这样写:
立即学习“Python免费学习笔记(深入)”;
import bcrypt
<h1>假设从 DB 取出的哈希是字符串形式</h1><p>stored_hash = "b$2b$12$..." # 来自数据库
password_attempt = request.form.get("password")</p><p>if bcrypt.checkpw(password_attempt.encode('utf-8'), stored_hash.encode('utf-8')):</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts"><img
src="https://img.php.cn/upload/skill/000/000/081/179231049778675.jpg" alt="361SCAN security check your skill python scripts" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts">361SCAN security check your skill python scripts</a>
<p>扫描并分析已安装技能,用于查看指定技能目录的名称、描述和详情,或列出所有已安装的技能。</p>
</div>
<a href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><h1>登录成功</h1><p>else:</p><h1>失败</h1><p>- 两个参数都必须是
bytes,所以明文密码和存储哈希都要.encode('utf-8') - 如果数据库存的是 str,不能跳过 encode 步骤;否则
checkpw()会报TypeError: Unicode-objects must be encoded before hashing - 不要尝试用
hashpw()重新计算再比较——checkpw()才是唯一正确入口
别把哈希存在 Flask session 或客户端 Cookie 里
哈希值虽不可逆,但它代表用户身份凭证。一旦泄露,攻击者可直接用于登录(即“哈希重放”)。Flask 的 session 默认签名不加密,若被篡改或泄露,等于交出钥匙。
- 密码哈希只应存于服务端受控数据库,且字段权限严格限制(如 PostgreSQL 的
GRANT SELECT ON users(pass_hash) TO app_user) - 登录成功后,用 Flask-Login 管理
current_user,它基于 session ID 查用户,而非存哈希本身 - 避免在日志里打印完整哈希(哪怕只是调试),防止意外落盘
升级哈希算法时要兼容旧数据,别强制所有用户重设密码
未来某天你可能想迁移到 argon2 或提高 bcrypt 的 rounds。此时不能要求全量用户立刻改密——体验差,还可能触发风控拦截。
可行方案是在用户下次登录时渐进升级:
- 读取数据库哈希,检查是否以
$2b$14$开头(表示新轮数) - 如果不是,用
checkpw()验证旧哈希;验证通过后,立即用新参数重新哈希并更新数据库字段 - 注意并发写入风险:同一用户多次登录请求可能同时触发更新,建议加数据库行级锁或用
UPDATE ... WHERE pass_hash = ?条件更新
哈希格式本身包含算法、轮数、盐等元信息,bcrypt 能自动识别——这是它比裸 pbkdf2 更省心的地方。

















