Password security encryption: use Werkzeug for salted hash storage
📂 Stage: Stage 3 - User System (Security)
🔗 Related chapters: Flask-Login 实战 · 数据验证
1. Absolutely prohibited practices
Security is no small matter, and password storage is the "first line of life and death" for web applications. Once the following three errors occur, user data will be leaked at least, and compliance penalties will be faced at worst:
# ❌ 灾难级:明文存储(等于把钥匙直接放在小偷面前)
user.password = request.form["password"]
# ❌ 无效级:简单哈希(MD5/SHA1 早已被彩虹表库「攻破」成百上千次)
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()
# ❌ 自欺欺人级:固定盐值(所有用户共用同一个盐,还是一破全破)
salt = "myapp_fixed_salt_12345"
hashed = hashlib.sha256((password + salt).encode()).hexdigest()
**Security core principles: slow hashing algorithm + independent random salt for each user + professional security library (such as Werkzeug) **
Werkzeug is the underlying dependency library of Flask. It comes with a set of password security tools verified by OWASP. There is no need to invent our own wheels at all:
2.1 Two core functions are enough
Werkzeug encapsulates the complex salting, iteration, and verification logic in two functions:
from werkzeug.security import generate_password_hash, check_password_hash
# 1️⃣ 注册时生成带随机盐的安全哈希
# 默认格式:pbkdf2:sha256:600000$随机盐$最终哈希(三段式,全存数据库就行)
password_hash = generate_password_hash("MyStrongPassword_2024!")
# 2️⃣ 登录时验证密码(自动解析哈希格式、迭代次数、盐值)
is_valid = check_password_hash(password_hash, "MyStrongPassword_2024!")
print(is_valid) # ✅ True
print(check_password_hash(password_hash, "wrongpass")) # ❌ False
2.2 How to directly connect to the database
The following is the scenario code that best fits the Flask/SQLAlchemy project:
# 注册接口(节选)
from app.models import User
from app.extensions import db
@app.route("/register", methods=["POST"])
def register():
email = request.form["email"]
password = request.form["password"]
# 先检查邮箱是否已存在,再生成哈希
if User.query.filter_by(email=email).first():
return "邮箱已注册", 400
# ✅ 直接调用 Werkzeug 工具
new_user = User(
email=email,
password_hash=generate_password_hash(password)
)
db.session.add(new_user)
db.session.commit()
return "注册成功", 201
# 登录接口(节选)
@app.route("/login", methods=["POST"])
def login():
email = request.form["email"]
password = request.form["password"]
user = User.query.filter_by(email=email).first()
# ✅ 统一的错误提示(防止枚举用户)
if not user or not check_password_hash(user.password_hash, password):
return "邮箱或密码错误", 401
# 登录成功后的逻辑(比如设置 Flask-Login 的 session)
return "登录成功", 200
2.3 Optional stronger hash algorithm
If you have extremely high security requirements (such as financial and medical applications), you can replace Werkzeug's default algorithm:
# 1️⃣ 默认算法 pbkdf2:sha256(60万次迭代,平衡了安全与性能)
generate_password_hash("password", method="pbkdf2:sha256")
# 2️⃣ 更安全的 scrypt(内存密集型,GPU/ASIC 破解难度大)
generate_password_hash("password", method="scrypt")
# 3️⃣ 经典的 bcrypt(需要单独安装依赖 pip install bcrypt)
generate_password_hash("password", method="bcrypt")
3. More elegant encapsulation: complete User model
Encapsulating the password hash and verification logic inside the User model can avoid writing errors in the business layer and make the code cleaner:
# app/models/user.py
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin # 如果用 Flask-Login
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, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(256), nullable=False) # 留够空间存三段式哈希
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# ⚠️ 禁止直接读取密码(抛出错误提醒开发者)
@property
def password(self):
raise AttributeError("密码是私有属性,不可直接读取!")
# ✅ 设置密码时自动哈希(业务层只需要 user.password = "xxx")
@password.setter
def password(self, plaintext):
self.password_hash = generate_password_hash(plaintext)
# ✅ 验证密码的模型方法(业务层只需要 user.check_password("xxx"))
def check_password(self, plaintext):
return check_password_hash(self.password_hash, plaintext)
def __repr__(self):
return f"<User {self.email}>"
After encapsulation, the code of the business layer will be more concise:
# 注册时(直接用属性赋值)
new_user = User(email="test@example.com", password="MyStrongPassword_2024!")
# 登录时(直接用模型方法)
if user and user.check_password(request.form["password"]):
# 登录成功
4. Security Best Practice Checklist
Werkzeug alone is not enough, you also need to cooperate with the following security details:
✅ Must do
- Always use professional security libraries (Werkzeug, bcrypt, passlib, etc.)
- Completely prohibit clear text/reversible encrypted storage of passwords
- Passwords and password hashes should never appear in logs or error messages
- When login verification fails, "email or password error" will be returned uniformly (to prevent attackers from enumerating users)
- The production environment must use HTTPS throughout the entire process (to prevent middlemen from stealing passwords)
- The database field length should be at least 256 bytes (to prevent the three-segment hash from being truncated)
❌ Absolutely Avoid
- Implement the hash algorithm and salt value generation logic yourself
- Use MD5, SHA1, and SHA256 for password hashing (these are "fast hashes" specially designed to speed up cracking)
- Use a fixed salt value or extract the salt value from the username/email
- Hardcode the salt or encryption key in your code
- The password length limit is too short (at least 8 characters, it is recommended to meet the character complexity requirements)
5. One-minute summary
The core of secure password storage is actually this "two-step process":
from werkzeug.security import generate_password_hash, check_password_hash
# 📝 注册/修改密码时:生成带随机盐的慢哈希
password_hash = generate_password_hash("your_plain_password")
# 🔑 登录时:自动解析哈希验证
if check_password_hash(user.password_hash, "your_input_password"):
print("验证成功!")
💡 Final reminder: Password hashing is only the first line of defense. It must also be combined with measures such as "HTTPS transmission", "verification code to prevent brute force cracking", "password strength verification" and "password expiration reminder" to build a complete user system security system.
🔗 Extended reading