django-fernet-encrypted-fields是最稳妥的开箱即用加密方案,封装Fernet细节避免AES模式、IV管理等陷阱;需完整覆盖字段生命周期四个方法,密钥须从环境变量读取或用PBKDF2衍生,加密字段不支持LIKE查询,模糊搜索需额外哈希字段,批量加密存量数据应使用带分批与事务控制的管理命令。
django-fernet-encrypted-fields 是目前最稳妥、开箱即用的方案,它封装了 Fernet(AES-128-CBC + HMAC-SHA256)的全部细节,避免你自己踩 AES 模式、IV 管理、padding、tag 校验等坑。不推荐手写加密字段,除非你明确需要 GCM 模式或密钥轮换策略。
为什么不能直接重载 CharField.to_python?
很多开发者试图在普通字段里加解密逻辑,结果出现:admin 页面显示乱码、filter(key__icontains='abc') 返回空、bulk_create 后数据明文入库、json 序列化失败。根本原因是 django 字段生命周期中,from_db_value(从 db 读取后调用)、to_python(反序列化时调用)、get_prep_value(存入前预处理)、get_db_prep_save(保存前最终处理)四个方法必须全部覆盖。漏掉任意一个,orm 缓存或批量操作就会绕过加密逻辑。
ENCRYPTION_KEY 必须从环境变量或 SECRET_KEY 衍生
硬编码密钥如 b'x'*32 或直接用 os.environ.get('KEY') 都是高危操作:
- 生产环境密钥必须通过
os.environ.get('DJANGO_ENCRYPTION_KEY')读取,且需校验长度是否为 32 字节(Fernet 要求) - 若想复用
settings.SECRET_KEY,必须用pbkdf2_hmac('sha256', SECRET_KEY.encode(), b'django-fernet-v1', 100_000, dklen=32)衍生,否则不同部署节点间无法互通 - 密钥一旦写死进代码或迁移文件,轮换时旧数据将永久不可解
加密字段无法做 LIKE 查询,模糊搜索得靠额外哈希字段
数据库看到的只是密文 blob,__icontains、__startswith 全部失效。如果业务真要按密钥前缀查用户,必须加一个辅助字段:
search_hash = models.CharField(max_length=64, db_index=True)
写入时同步计算:hashlib.sha256(value.strip().lower().encode()).hexdigest(),查询走 search_hash__exact。注意:这个哈希不可逆,仅用于等值匹配,不能还原原始值。
批量加密存量数据要用自定义 manage.py 命令
已有明文数据上线后补加密,不能靠 ORM 单条 save——太慢且易中断。推荐写管理命令,核心逻辑是:
立即学习“Python免费学习笔记(深入)”;
- 用
iterator(chunk_size=2000)分批读取,避免内存爆掉 - 对每批记录用
bulk_update(..., fields=['key'])批量更新,而非逐条save() - 务必在事务外加锁表或停写,防止加密中途有新写入导致混杂明密文
- 执行前备份全库,因为密钥错误会导致整批数据解密失败
真正麻烦的不是加密动作本身,而是密钥保管、轮换设计和查询降级方案——这些比写几行 EncryptedTextField 要花十倍精力。


















