推荐使用 pbkdf2:sha256 或 bcrypt;前者兼容性好且为 Flask 默认,后者需安装 bcrypt 包但安全性更高;禁用 sha1/md5,注意哈希字段长度至少 VARCHAR(128) 并避免截断或编码错误。

用 generate_password_hash() 加密密码时,算法选哪个?
默认用的是 pbkdf2:sha256,够用但不是最强;如果项目对安全敏感,建议显式指定 pbkdf2:sha256 或 bcrypt(需装 bcrypt 包)。Werkzeug 本身不带 bcrypt,只提供接口,底层靠第三方库实现。
常见错误是直接传明文进数据库,或自己拼接 salt —— generate_password_hash() 已内置随机 salt,重复调用结果不同,别试图“固定 salt”来测试。
-
generate_password_hash("123", method="pbkdf2:sha256", salt_length=16):推荐组合,兼容性好,Flask 默认就走这条路 - 想用
bcrypt?先pip install bcrypt,再写method="bcrypt";注意 Windows 下编译可能报错,这时候回退到pbkdf2更省事 - 别碰
sha1或md5:Werkzeug 虽然还留着接口,但已标记为 deprecated,验证时会警告
check_password_hash() 验证失败的三个高频原因
它只返回 True 或 False,不抛异常,所以失败容易被静默忽略。最常踩的坑不是逻辑错,而是数据没对齐。
- 哈希值从数据库读出来时被截断了:
password_hash字段长度至少留 128 字符(pbkdf2输出约 90+ 字符,bcrypt约 60),用VARCHAR(128)或更长,别用CHAR(32) - 存哈希时用了
.strip()或意外转了编码:generate_password_hash()返回str,不是bytes,入库前别 encode 成 utf-8 再 decode 回来,会坏掉 - 验证时传了空字符串或
None:check_password_hash(None, "xxx")直接返回False,不会报错,得在调用前加一层非空判断
Flask-login 集成时,check_password_hash() 放哪儿最合适?
别塞进 User.load_user(),那里只负责查用户,不该做密码比对;也别放在登录路由里硬写一遍逻辑 —— 违反关注点分离,测起来麻烦。
标准做法是在自定义的 User 类里加个方法,比如 verify_password(),里面调 check_password_hash(self.password_hash, password)。这样 login_user() 前就能干净地验证。
- 示例:
def verify_password(self, password): return check_password_hash(self.password_hash, password) - 登录视图里就一行:
if user and user.verify_password(form.password.data): - 别把哈希逻辑和数据库查询耦合进同一个函数,调试时分不清是查不到用户,还是密码错了
升级哈希算法时怎么平滑迁移?
不能强制所有用户重设密码。老用户登录时,先用旧算法试,失败再用新算法试;如果旧算法匹配成功,立刻用新算法重新哈希并更新数据库字段。
- 假设你从
pbkdf2:sha256升级到pbkdf2:sha512,验证逻辑要像这样:if check_password_hash(user.password_hash, password): # 旧算法匹配,且 hash 不是新的,就升级 if not user.password_hash.startswith("pbkdf2:sha512$"): user.password_hash = generate_password_hash(password, method="pbkdf2:sha512") db.session.commit() - 关键点:必须检查哈希字符串前缀,而不是靠字段加时间戳之类不可靠方式判断是否已升级
- 别忘了给
password_hash字段留足长度 ——sha512比sha256输出略长,字段不够会 silently 截断
哈希升级看着简单,实际最容易漏的是字段长度和前缀判断逻辑,上线前务必用老账号实测一遍登录路径。










