不能直接在model字段上用decrypt()方法,因为django orm读取字段是惰性的,且绕过python属性访问,直接调用from_db_value、to_python等钩子;漏掉任一钩子会导致values()、drf序列化等场景暴露密文。

为什么不能直接在 Model 字段上用 decrypt() 方法?
因为 Django 的字段读取是惰性的,model.field 返回的是原始数据库值(比如 AES 加密后的 bytes 或 base64 字符串),不是解密后的明文。你不能靠覆盖 __get__ 就完事——Django ORM 在查询集构建、序列化、表单绑定等环节会绕过 Python 属性访问,直接操作字段的 from_db_value() 和 to_python() 钩子。跳过这些钩子,加密字段在 values()、values_list()、DRF 序列化时就会暴露密文。
必须重写的四个核心方法:from_db_value / to_python / get_prep_value / to_database
自定义加密字段的本质,是让加解密逻辑嵌入到 Django 的类型转换流水线中。这四个方法缺一不可:
-
from_db_value(self, value, expression, connection):从数据库读出后立即解密,确保model.field拿到的是明文; -
to_python(self, value):处理 form.cleaned_data、admin 表单提交、setattr()等场景,把输入转成明文(若已是明文则跳过); -
get_prep_value(self, value):在保存前加密,返回密文供数据库写入; -
to_database(self, value)(Django 5.0+ 新增):替代旧版的get_db_prep_save(),更明确地控制写入数据库的值,必须返回加密后的内容(bytes 或 str)。
漏掉 to_database 是 Django 5.0 下最常见失败点——字段看似能读,但 save() 后数据库里存的还是明文,或报 TypeError: expected str, bytes or bytearray。
如何安全处理密钥和加解密逻辑?
别把密钥硬编码进字段类,也别依赖 settings 模块全局变量(测试难 mock、多租户不安全)。推荐做法:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 密钥通过字段实例参数传入:
EncryptedCharField(key=secrets.token_bytes(32)); - 加解密用
cryptography.hazmat.primitives.ciphers(非pycryptodome),它支持 AEAD 模式(如 AES-GCM),能同时保证机密性与完整性; - 每次加密必须用唯一 nonce(随机数),并和密文一起存入数据库(例如 base64(nonce)+base64(ciphertext));
- 解密失败时(比如密钥错、nonce 损坏),
from_db_value必须返回None或抛ValueError,不能静默返回空字符串——否则数据损坏难以察觉。
示例片段(仅核心逻辑):
def from_db_value(self, value, expression, connection):
if value is None:
return value
try:
raw = base64.b64decode(value)
nonce, ciphertext = raw[:12], raw[12:]
decryptor = Cipher(AES(self.key), GCM(nonce), backend).decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
except Exception:
raise ValueError("Decryption failed for field")
迁移、索引与查询限制必须提前确认
加密字段天然不支持数据库原生查询(__contains、__exact 都失效),因为密文无序且不可比较。这意味着:
- 不能在该字段上建普通 B-Tree 索引,WHERE 条件只能靠 Python 层 filter(性能差);
- 迁移时如果已有明文数据,需手动写
RunPython操作批量加密,不能只改字段类型; - admin 中搜索框、
list_filter对该字段无效,需配合search_fields去掉或改用其他可索引字段辅助; - JSONField 或 ArrayField 内部含加密字段?不行——Django 不会递归调用子字段的
from_db_value。
真正要用加密字段,得接受「这个字段只读不查」的前提;否则就得换方案,比如应用层透明加密(如 django-cryptography)或数据库端加密(PostgreSQL pgcrypto)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










