python-3.x - SQLAlchemy:调用函数并将返回值始终保存在表中
问题描述
我有一个用户表,它有一个支付密码。我决定加密所有这些信息。每个 API 检索或插入新数据,它总是像这样调用一个函数来加密密码。
from cryption import encrypt, decrypt
enc_password = encrypt(password)
data = User(id=id, password=enc_password)
db.add(data)
db.commit()
由于我在 API 中对此进行了加密,因此它看起来是多余的。此外,有时我忘记添加加密代码,这会导致严重错误。所以,我想知道我是否可以在模型上这样做而不是这样做。如果我可以在插入数据之前进行加密,并在查询级别返回数据时进行解密,那就太好了。
解决方案
首先,一个重要的警告:
警告:永远不要存储用户的密码。从不。一旦您的服务器遭到入侵,黑客不仅会拥有您的服务器,他们还会拥有加密密钥和所有密码。
相反,存储密码哈希。请参阅为什么密码散列被认为如此重要?以及散列密码和加密密码之间的区别。
在 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)
这在 SQLAlchemy 手册的Using Descriptors and Hybrids中有介绍。请注意,在这种情况下使用 a 没有意义hybrid_property
,因为您的数据库无法在服务器端加密和解密。
如果您确实加密,请将您的密钥与源代码分开,并确保您轮换密钥(保留旧密钥以解密尚未重新加密的旧数据),以便如果密钥被泄露,您至少可以代替它。
选择一个好的、值得信赖的加密配方。该cryptography
软件包包括 Fernet 配方,请参阅我的另一个答案以获取有关如何使用它的建议,然后升级到MultiFernet()
该类以管理密钥轮换。
此外,阅读密钥管理最佳实践。密码学很容易出错。
推荐阅读
- typescript - 为什么 else if 子句会导致“此条件将始终返回 'true'”错误?
- r - 将 NA 分配给特定行
- freeradius - 如何在 Debian 10 上的 freeradius (3.0) 中配置会计
- python - 无法在 Windows 10 中使用 Pipenv .env 文件定义空环境变量
- javascript - 计算通过了多少 discord.js PM
- python - 使用 Python 过滤多字典中的值
- wordpress - 如何解决 wordpress 中调整 iamge 大小的问题
- qt - 无法编译自己的客户端构建
- angular - Apple 使用 Firebase 登录,并且 Angular 弹出窗口不会重定向到主页
- visual-c++ - “玩家”:未知的覆盖说明符