You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django实现字段加密存储与查询自动解密的方案求助

解决方案:自定义加密字段(推荐)

直接封装一个支持自动加密/解密的Django模型字段,这是最符合Django规范的做法,能自动处理保存加密和查询解密,无需手动重写save或依赖额外属性。

实现自定义Fernet加密字段

from django.db import models
from cryptography.fernet import Fernet
import os

# 建议从环境变量读取密钥,禁止硬编码
cipher_suite = Fernet(os.environ.get("FERNET_ENCRYPTION_KEY").encode())

class EncryptedTextField(models.TextField):
    def from_db_value(self, value, expression, connection):
        # 从数据库读取值时自动解密
        if value is None:
            return value
        try:
            return cipher_suite.decrypt(value.encode()).decode()
        except Exception:
            # 兼容未加密的旧数据或解密失败场景
            return value

    def to_python(self, value):
        # 转换Python对象时解密(如表单提交值)
        if isinstance(value, str):
            return value
        if value is None:
            return value
        try:
            return cipher_suite.decrypt(value).decode()
        except Exception:
            return value

    def get_prep_value(self, value):
        # 保存到数据库前自动加密
        if value is None:
            return value
        if isinstance(value, str):
            return cipher_suite.encrypt(value.encode()).decode()
        return value

在模型中使用该字段

替换原有字段类型即可,无需再手动处理加密逻辑:

class YourModel(models.Model):
    first_name = EncryptedTextField()
    # 其他模型字段...

此后无论是保存数据还是查询数据,字段都会自动完成加密/解密操作,完全无需额外代码。


临时过渡方案(不推荐长期使用)

如果暂时无法修改字段类型,可以通过以下方式实现查询自动解密:

重写模型__getattribute__方法

class YourModel(models.Model):
    first_name = models.TextField()

    def save(self, *args, **kwargs):
        if isinstance(self.first_name, str):
            self.first_name = cipher_suite.encrypt(self.first_name.encode()).decode()
        super().save(*args, **kwargs)

    def __getattribute__(self, name):
        value = super().__getattribute__(name)
        if name == "first_name" and value:
            try:
                return cipher_suite.decrypt(value.encode()).decode()
            except Exception:
                return value
        return value

注意:此方法会拦截所有first_name属性的访问,需做好异常捕获避免程序崩溃。

自定义QuerySet批量解密

class YourModelQuerySet(models.QuerySet):
    def decrypt_first_name(self):
        for instance in self:
            if instance.first_name:
                try:
                    instance.first_name = cipher_suite.decrypt(instance.first_name.encode()).decode()
                except Exception:
                    pass
        return self

class YourModel(models.Model):
    first_name = models.TextField()
    objects = YourModelQuerySet.as_manager()

    # 保留原save方法

使用时直接调用自定义QuerySet方法:

# 查询后自动解密所有实例的first_name
instances = YourModel.objects.all().decrypt_first_name()
for obj in instances:
    print(obj.first_name)  # 已解密的明文

关键注意事项
  • 密钥必须妥善保管,丢失密钥会导致加密数据永久无法解密,建议使用环境变量或专业密钥管理服务存储。
  • 必须添加异常捕获逻辑,兼容未加密的旧数据、密钥变更等异常场景。
  • 自定义字段方案是长期维护的最优选择,避免重复代码,完全符合Django的设计模式。

内容的提问来源于stack exchange,提问作者Redgrave

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:51:05