应使用Werkzeug的generate_password_hash生成密码哈希,因其默认带盐、自动迭代且输出自解析字符串;禁用hashlib裸哈希、md5/sha1及手动pepper,method和salt_length须显式指定,数据库字段至少String(128),校验失败时优先检查哈希完整性。

直接用 generate_password_hash 生成哈希,别碰 hashlib
明文密码进数据库等于把钥匙挂在门把手上——一旦脱库,所有账户立刻沦陷。Werkzeug 的 generate_password_hash 是 Flask 生态里最轻量、最可靠的选择,它默认带盐、自动迭代、输出自解析字符串;而 hashlib.sha256 这类裸哈希不加盐、无迭代、盐值固定,check_password_hash 根本无法识别,校验必然失败。
- 别自己拼接 salt + hash:Werkzeug 输出形如
pbkdf2:sha256:600000$salt$hash,整串必须原样存,切片或 strip 会破坏结构 - 别用
md5或sha1:这些算法已被证明可被快速碰撞,且不支持迭代调参 - 别在应用层手动加 pepper(硬编码密钥):Werkzeug 不支持该参数,强行注入反而破坏盐随机性
method 和 salt_length 必须显式指定
Werkzeug 版本升级可能悄悄改默认参数,比如 Flask 2.2 → 2.3 后默认 rounds 从 290000 降到 260000,老哈希仍能校验,但新生成的哈希在旧环境可能不识别。依赖隐式默认值等于把安全交给运气。
- 推荐写死:
method="pbkdf2:sha256:600000"(60 万次迭代,当前安全基线) -
salt_length=32:避免用默认的 16 字节,更抗盐碰撞和彩虹表攻击 - 数据库字段至少设为
String(128):pbkdf2 输出约 112 字符,VARCHAR(64)必然截断导致校验永远返回False
check_password_hash 失败?先查这三件事
这个函数不报错,只静默返回 False,调试时极易误判为“密码输错了”。真实原因往往藏在数据流转环节。
- 从数据库读出的哈希值是否被
strip()或rstrip('\n')过?哪怕一个空格都会让比对失败 - 用户粘贴密码时是否混入零宽空格(
\u200b)或 BOM 头?request.form.get('password')拿到的就是脏数据 - 是否误把旧系统 bcrypt 哈希(以
$2b$开头)喂给check_password_hash?它只认 pbkdf2/argon2 格式,不兼容就直接返回False
模型层怎么封装才不容易翻车
把哈希逻辑塞进 ORM 模型的 setter 里,能天然隔离明文暴露风险,但要注意字段命名和属性控制。
立即学习“Python免费学习笔记(深入)”;
- 密码字段名别叫
password,改用password_hash,避免语义混淆 - 明文属性必须设为只写:
@password.setter可赋值,@property里直接raise AttributeError - 校验方法别写成
def check_password(self, raw),而应叫verify_password,和 Werkzeug 命名习惯对齐,降低协作认知成本
check_password_hash 就不会告诉你哪里错了,只会安静地拒绝登录。


















