最近写了几篇Flask相关的文章,今天继续分享
几乎所有业务系统都会涉及账号登录功能,而密码的安全存储是软件设计者必须要考虑的问题,相信没人还在用明文保存吧。
但初学者甚至一些有经验的程序员还停留在自己实现加密算法或者是用md5这样的算法对用户的明文密码进行加密存储。
md5早在2010年,美国软件工程学会(SEI)就认为MD5算法已被破解,黑客可以使用彩虹表、暴力穷举进行hash碰撞就能轻易破解。因为md5,sha这样的算法速度过于太快,对于一个长度6位的密码在一台普通电脑上不到1分钟就能穷举出来。
所以md5,sha算法一般适用于做数据完整性检查,比如软件签名。
Bcrypt
而目前主流的安全的hash算法就是Bcrypt,它的速度非常慢,加密一次大概需要0.3秒, 这对于正常的业务场景中,0.3秒是完全能接受的。而md5只需要1微秒,两者速度上相差几十万倍。
Bcrypt算法生成的值由4部分组成:
- 2a表示Bcrypt算法
- rounds: 是轮值因子,代表执行hash的次数,数值越高越安全,默认是10
- salt: 盐,一个128bits随机字符串,22字符
- Hash: 经过明文密码盐salt进行hash得到的值
因为bcrypt采用了一系列各种不同的Blowfish加密算法,并引入了一个 rounds factor,这个轮值因子决定了这个算法的代价有多大。这个算法不会因为计算机CPU处理速度变快而导致算法的时间变短。所以几乎不可能靠穷举来破解法。 对于一个6位的随机密码,穷举法要花上20年。
flask 最佳实践
flask社区已经由现成集成好的bcrypt库,直接安装
pip install flask-bcrypt
用工厂方法延迟初始化flask-bcrypt
# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from .config import config_by_name
flask_bcrypt = Bcrypt()
def create_app(config_name):
app = Flask(__name__)
...
flask_bcrypt.init_app(app)
...
return app
在models中的User类中,只需要提供两个方法,一个用来生成密码,另一个用于校验密码是否正确。
# model.py
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(255), unique=True, nullable=False)
username = db.Column(db.String(50), unique=True)
password_hash = db.Column(db.String(100))
@property
def password(self):
raise AttributeError('password: write-only field')
@password.setter
def password(self, password):
# 用来保存密码hash值
self.password_hash = flask_bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
"""
return boolean
"""
# 判断传过来的密码是否与数据库存的密码一致
return flask_bcrypt.check_password_hash(self.password_hash, password)
这里的 password 其实是一个只写不可读的property属性,数据库中实际存储的字段是password_hash
# view.py
@app.route("/login", method=["POST"])
def login():
....
user = User.query.filter_by(username=username).first()
is_success = user.check_password(request.data.get("password"))
if not is_success:
return {"msg":"密码错误"}
else:
# 密码正确
pass
@app.route("/register", method=["POST"])
def register():
password = request.data.get("password")
username = request.data.get("username")
user = User(username=username)
user.password = password
db.session.add(user)
db.session.commit()
关注公众号「Python之禅」,回复「1024」免费获取Python资源