首先,一个重要的警告:
警告:切勿存储用户密码。 从不。一旦您的服务器遭到入侵,黑客不仅会拥有您的服务器,还会拥有加密密钥和所有密码。
相反,存储密码哈希。见Why is password hashing considered so important? 和Difference between Hashing a Password and Encrypting it。
在 Python 中,使用 passlib 为您处理密码哈希。
这样一来,您就可以自动将密码哈希存储在数据库中,或进行任何其他数据转换,方法是使用属性进行转换。我在我的用户模型中使用它。
在以下示例模型中,password 属性实际上将 password_hash 列设置为哈希值:
from passlib.context import CryptContext
PASSLIB_CONTEXT = CryptContext(
# in a new application with no previous schemes, start with pbkdf2 SHA512
schemes=["pbkdf2_sha512"],
deprecated="auto",
)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
# site-specific fields, like name, email, etc.
# password hash handling
# pbkdf2-sha512 is 130 characters short, but it never hurts to
# leave space for future growth of hash lengths.
password_hash = db.Column(db.String(256), nullable=False)
def __init__(self, password=None, password_hash=None, **kwargs):
if password_hash is None and password is not None:
password_hash = self.generate_hash(password)
super().__init__(password_hash=password_hash, **kwargs)
@property
def password(self):
raise AttributeError("User.password is write-only")
@password.setter
def password(self, password):
self.password_hash = self.generate_hash(password)
def verify_password(self, password):
return PASSLIB_CONTEXT.verify(password, self.password_hash)
@staticmethod
def generate_hash(password):
"""Generate a secure password hash from a new password"""
return PASSLIB_CONTEXT.hash(password.encode("utf8"))
这让我可以使用User(..., password=password),它会自动为我计算密码。或者使用if new_password == new_password_again and some_user.verify_password(old_password): some_user.password = new_password 更新密码,而无需记住如何再次对密码进行哈希处理。
您还可以使用“隐藏”列来存储您需要在存储时加密、在检索时解密的任何其他数据。使用前导 _ 下划线命名您的模型属性以将它们标记为 API 私有,然后将真实列名作为第一个参数传递给 Column() 对象。然后使用一个属性对象来处理加密和解密:
class Foo(db.Model):
__tablename__ = "foo"
id = db.Column(db.Integer, primary_key=True)
# encrypted column, named "bar" in the "foo" table
_bar = db.Column("bar", db.String(256), nullable=False)
@property
def bar(self):
"""The bar value, decrypted automatically"""
return decrypt(self._bar)
@bar.setter
def bar(self, value):
"""Set the bar value, encrypting it before storing"""
self._bar = encrypt(bar)
这在Using Descriptors and Hybrids 下的 SQLAlchemy 手册中有介绍。请注意,在这种情况下使用 hybrid_property 是没有意义的,因为您的数据库无法在服务器端加密和解密。
如果您进行加密,将您的密钥与源代码分开,并确保轮换您的密钥(保留旧密钥以解密尚未重新加密的旧数据),以便如果密钥被泄露,您至少可以替换它。
选择一个好的、受信任的加密方案。 cryptography 包包含 Fernet 配方,请参阅 another answer of mine 获取有关如何使用它的建议,然后升级到 MultiFernet() class 以管理密钥轮换。
另外,请阅读key management best practices。密码学很容易出错。