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列设置为Hashed值:

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手册中涵盖了这一点。请注意,在这种情况下,使用hybrid_property是没有意义的,因为您的数据库无法在服务器端进行加密并解密。

如果您进行了加密,将密钥与源代码保持分开如果钥匙被妥协,您至少可以更换它。

选择一个好的,值得信赖的加密配方。cryptography软件包包括Fernet食谱,有关如何使用的建议,请参阅我的另一个答案,然后升级到MultiFernet()类以管理密钥旋转。

此外,请阅读关键管理最佳实践。加密很容易犯错。

最新更新