密码安全加密:使用 Werkzeug 进行加盐哈希存储

📂 所属阶段:第三阶段 — 用户系统(安全篇)
🔗 相关章节:Flask-Login 实战 · 数据验证


1. 绝对禁止的做法

# ❌ 明文存储(灾难!)
user.password = request.form["password"]

# ❌ 简单哈希(MD5/SHA1 可被彩虹表秒破)
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()

# ❌ 固定盐值(所有用户同一个盐)
salt = "myapp_salt"
hashed = hashlib.sha256((password + salt).encode()).hexdigest()

正确做法:慢哈希 + 随机盐 + Werkzeug


2. Werkzeug 哈希工具

2.1 核心函数

from werkzeug.security import generate_password_hash, check_password_hash

# 生成哈希
password_hash = generate_password_hash("MyPassword123!")
# 结果示例:pbkdf2:sha256:600000$salt$hash...

# 验证密码
is_correct = check_password_hash(password_hash, "MyPassword123!")
print(is_correct)  # True

2.2 生成安全哈希

# 注册用户时
def create_user(email, password):
    user = User(
        email=email,
        # 生成带随机盐的安全哈希
        password_hash=generate_password_hash(password),
    )
    db.session.add(user)
    db.session.commit()

# 登录验证时
def authenticate_user(email, password):
    user = User.query.filter_by(email=email).first()
    if user and check_password_hash(user.password_hash, password):
        return user
    return None

2.3 哈希算法说明

# Werkzeug 默认使用 pbkdf2:sha256
# 格式:pbkdf2:sha256:迭代次数$盐$哈希

# 可选算法:
generate_password_hash("password", method="pbkdf2:sha256")
generate_password_hash("password", method="scrypt")     # 更安全,但慢
generate_password_hash("password", method="bcrypt")     # 需要 pip install bcrypt

3. 完整用户模型

# app/models/user.py
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from app.extensions import db

class User(UserMixin, db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # 密码由 User 类管理,不作为独立字段暴露
    @property
    def password(self):
        raise AttributeError("密码不可读!")

    @password.setter
    def password(self, plaintext):
        """设置密码时自动哈希"""
        self.password_hash = generate_password_hash(plaintext)

    # 验证密码
    def check_password(self, plaintext):
        return check_password_hash(self.password_hash, plaintext)

    def __repr__(self):
        return f"<User {self.email}>"

4. 安全最佳实践清单

✅ 推荐
1. 始终使用 Werkzeug 的 generate_password_hash
2. 不要存储明文密码
3. 不要在日志中记录密码
4. 密码验证失败时,不要告诉用户是"密码错"还是"用户不存在"(防止枚举攻击)
5. 生产环境使用 HTTPS 传输密码
6. 使用 `method="bcrypt"` 或 `method="scrypt"`(更强安全)

❌ 避免
1. 明文存储密码
2. 用 MD5/SHA1 做密码哈希
3. 使用固定盐值
4. 密码明文出现在错误信息中

5. 小结

from werkzeug.security import generate_password_hash, check_password_hash

# 注册时:生成哈希
password_hash = generate_password_hash(password)

# 登录时:验证
if check_password_hash(user.password_hash, password):
    # 登录成功

💡 记住:密码哈希是 Web 应用的安全底线。永远不要心存侥幸明文存储,Werkzeug 让这一切变得简单。


🔗 扩展阅读