使用 Django 自定义 Field 加密数据

作者:赖铭

导语:出于安全考虑,一些敏感信息是需要加密存入数据库的。这样即使被黑客脱库,也能最大限度的避免损失。

数据加密并不是难事,各种现成的加密库可以轻松实现高强度的加密。对于加密标准的选择,根据实际场景需要,在《IDC开发运维安全标准》 4.1算法标准 中选择合适算法即可。

在实际开发中,我是在业务现成逻辑代码已经大体完成时接手,被要求将部分字段加密处理的。因此,我希望加密逻辑对业务逻辑完全不可见,做到不需要修改任何业务逻辑代码,即可接入数据加密。

要做到对业务逻辑不可见,就需要在字段入库和出库时,自动进行加密和解密。Django的自定义Field提供的一些接口,可以在数据出库后、数据入库前,做一些特定的操作。如:DatetimeField,就是使用了这些接口,做到了可以直接使用python的datetime进行操作,免去了与数据库类型转换的工作。

下面这个例子,选择的是AES对称加密算法,对CharField进行加密处理。

class AESCharField(models.CharField):
"""
    在数据库中AES256加密的 CharField
    - 兼容未加密数据,加密后字符串会带上
    """

    def __init__(self, *args, **kwargs):
"""
        初始化
        :param prefix: 加密串前缀
        """
        if 'prefix' in kwargs:
self.prefix = kwargs['prefix']
del kwargs['prefix']
else:
self.prefix = "aes_str:::"

        self.cipher = AESCipher(settings.SECRET_KEY)
super(AESCharField, self).__init__(*args, **kwargs)

def deconstruct(self):
        name, path, args, kwargs = super(AESCharField, self).deconstruct()
if self.prefix != "aes_str:::":
            kwargs['prefix'] = self.prefix
return name, path, args, kwargs

def from_db_value(self, value, expression, connection, context):
"""
        出库后解密数据
        """
        if value is None:
return value
if value.startswith(self.prefix):
            value = value[len(self.prefix):]
            value = self.cipher.decrypt(value)

return value

def to_python(self, value):
"""
        反序列化和Form clean()时调用,解密数据
        """
        if value is None:
return value
elif value.startswith(self.prefix):
            value = value[len(self.prefix):]
            value = self.cipher.decrypt(value)

return value

def get_prep_value(self, value):
"""
        入库前加密数据
        """
        if isinstance(value, str) or isinstance(value, unicode):
            value = self.cipher.encrypt(value)
            value = self.prefix+value
elif value is not None:
raise TypeError(str(value)+" is not a valid value for AESCharField")

return value
  1. init中使用秘钥生成加密器,用于后面的加密,这里的秘钥是写在django settings中的。
  2. prefix是标志加密后字符串的前缀,用于区分加密前和加密后数据。为了兼容已有旧数据的情况。
  3. from_db_value是数据库返回值后,调用的函数。在此函数中,进行数据解密,需要处理None的情况。
  4. get_prep_value是数据入库前调用的函数。在此函数中,加密数据,加上前缀。需要判断None的情况。
  5. to_python是需要在反序列化和Form表单clean()时使用的,可能遇上加密后数据,也可能遇上未加密数据和None,需要注意判断。
  6. 这里兼容了存在旧数据的情况,因此需要使用startwith判断前缀是否匹配。

WARNING:

  1. AES加密算法,如果设置随机的向量iv,哪怕加密相同的明文,每次加密产生的密文也是不相同的,因此无法对加密后字段进行数据库查询操作,业务代码需要注意。如果需要实现数据库查询,需要固定向量iv,并且实现get_prep_lookup方法。
  2. 这里的写法兼容了可能存在的旧数据,如果不对这些旧数据进行操作,这些旧数据将保持明文状态。如果需要加密旧数据,只需要进行一次取值+存入操作,即可将旧数据加密。
  3. 注意max_length的设置。加密后的密文长度,会与明文有所出入,需要根据所选加密算法和前缀进行计算。 该例子只提供了基本的加解密功能,展示了如何编写Django的自定义Model Field,用途不仅仅是加密。如果需要更加复杂的功能,参照官方文档:https://docs.djangoproject.com/en/1.11/howto/custom-model-fields/

下面附上可以直接拿来使用的代码

使用说明

  1. 安装依赖six,pycrypto
  2. 将CharField直接换成AESCharField。
  3. 密文比明文长,注意修改max_length。AESCipher的predict_length(length)可以计算加密后的长度,最后要加上密文前缀的长度,默认前缀aes_str:::长度为10。即:AESCipher.predict_length(old_max_length)+len(prefix)
  4. 密文前缀默认为aes_str:::,可以通过添加参数prefix修改设置。class TestModel(models.Model): field1 = AESCharField("field1", max_length=100, prefix="orz:::")
  5. 使用的是Django的SECRET_KEY作为密钥,做了一次sha256。
  6. 由于兼容明文旧记录,旧记录至少需要一次保存操作,才能将明文替换为密文。如果需要立刻加密,可以通过脚本逐条保存一遍,注意避免与用户并发操作导致数据一致性问题。
  7. 使用随机向量iv的AES加密算法,加密后的数据无法进行查询,保证业务中没有以字段作为查询条件的语句,如果需要实现数据库查询,需要固定向量iv,并且实现get_prep_lookup方法。。
  8. 老版本Django使用south做同步,需要配置AESCharField的路径。具体可以参考south文档https://south.readthedocs.io/en/latest/customfields.html#custom-fieldsfrom south.modelsinspector import add_introspection_rules add_introspection_rules([], ["^mywebsite1_3\.aes\.AESCharField"]) # 示例

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿DD

JSON Web Token - 在Web应用间安全地传递信息

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

922
来自专栏FreeBuf

直面冥王:最新爆发的C#敲诈木马HadesLocker解读

近日哈勃分析系统捕获到一类由C#语言编写的新的敲诈勒索木马。之前出现 的C#语言编写的木马只是简单地调用了一些C#库来辅助开发。与之相比,这次的变种增加了多层嵌...

3066
来自专栏Kirito的技术分享

JSON Web Token - 在Web应用间安全地传递信息

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。 让我们来假想一下一个场景。在A用...

37610
来自专栏会跳舞的机器人

记一次系统密码安全事故以及修改方案

运营人员反馈在晚上十一点多收到系统后台登录的短信验证码,第二天在后台的操作日志中发现自已的账号有被登录过后台系统,但实际上自已并没有登录操作,怀疑账号被他人恶意...

1582
来自专栏北京马哥教育

Linux基于OpenSSL实现私有CA构建

前言 随着互联网的迅猛发展,网络通信已经成为传递信息的主要途径。而通信时的数据传输大部分却是明 文传输的,在网络这个不安全的环境下,如果没有一套数据加密机制,就...

3837
来自专栏PHP在线

JSON Web Token - 在Web应用间安全地传递信息

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

2187
来自专栏QQ音乐技术团队的专栏

分析 Android V2 新签名打包机制

本文实现了一种在 apk 的签名块中写入信息,读取信息,删除信息还原 apk 等功能,验证了在签名块中写入信息可以通过 v2 检验的例子。

2.6K0
来自专栏PHP在线

JSON Web Token - 在Web应用间安全地传递信息

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。 让我们来假想一下一个场景。在A用户...

3786
来自专栏PHP在线

PHP处理密码的几种方式

在使用PHP开发Web应用的中,很多的应用都会要求用户注册,而注册的时候就需要我们对用户的信息进行处理了,最常见的莫过于就是邮箱和密码了,本文意在讨论对密码的处...

1714
来自专栏Java架构沉思录

如何使用JWT向服务器证明你就是你

原文地址:http://blog.leapoahead.com/2015/09/06/understanding-jwt/

1354

扫码关注云+社区

领取腾讯云代金券