直接选 Fernet,除非明确需要 AEAD 且能自行管理 nonce;它内置密钥派生、随机 IV 和 HMAC 校验,避免手写 AES 的常见错误。

字段加密该用 Fernet 还是 AES-GCM?
直接选 Fernet,除非你明确需要认证加密(AEAD)且能自己管理 nonce。Fernet 是 cryptography 库封装好的高阶接口,内置密钥派生、随机 IV、HMAC 校验,避免手写 AES 时漏掉 encrypt-then-MAC 或重复使用 nonce 的致命错误。
常见错误现象:CryptographicException: Invalid MAC —— 多半是密钥错、数据被截断或用了不同密钥解密;ValueError: Nonce too short —— 混用了 AESGCM 和 Fernet 的序列化格式。
实操建议:
-
Fernet输出是 base64 编码的字节串,可直接存数据库TEXT字段,无需额外编码 - 密钥必须是 32 字节(256 位),用
base64.urlsafe_b64encode(os.urandom(32))生成,别手写字符串 - 不要把密钥硬编码在代码里,走环境变量或密钥管理服务(如 HashiCorp Vault)
如何安全地加密 Django/Flask 模型字段?
别改 ORM 的底层字段逻辑,容易破坏查询、迁移和序列化。推荐在模型属性层做透明加解密 —— 即定义 _encrypted_data 字段存密文,再用 @property + @xxx.setter 封装读写逻辑。
立即学习“Python免费学习笔记(深入)”;
使用场景:用户身份证号、手机号、地址等 PII 字段,需满足 GDPR 或等保要求。
实操建议:
- 加密发生在 Python 层,数据库看到的只是密文,不依赖数据库加密函数(如 MySQL
AES_ENCRYPT),避免跨库迁移问题 - 解密只在业务逻辑需要明文时触发,避免 ORM
select_related或序列化时批量解密拖慢响应 - 加解密操作不参与数据库事务,但密文写入失败时应抛出异常,防止“写明文没报错、实际存了空值”
加密后还能查 LIKE 或排序吗?
不能。Fernet 加密后是确定性随机输出,相同原文每次加密结果都不同(因为随机 IV),所以无法做模糊匹配、范围查询或 ORDER BY。
如果你真需要按加密字段搜索,只有两个现实选项:
- 用
hazmat.primitives.ciphers.modes.ECB(不推荐)—— ECB 模式下相同明文块永远输出相同密文块,但会泄露数据模式,严重违背加密原则 - 引入确定性加密方案,比如用
AES-SIV(cryptography 3.4+ 支持ChaCha20Poly1305不行,得上AEADwith fixed nonce + context)—— 但必须确保输入唯一性(如拼接主键),否则碰撞风险上升 - 更稳妥的做法:对搜索字段单独建哈希索引(如
sha256(phone + salt)),哈希不可逆但支持等值查询
密钥轮换时怎么处理存量密文?
没有银弹。Fernet 不支持密钥升级解密,旧密钥加密的数据只能用旧密钥解。这意味着轮换时必须保留旧密钥,并在解密逻辑中尝试多个密钥。
实操建议:
- 在密文前加 1 字节版本标识(如
b'\x01'),解密时先读头,再路由到对应密钥实例 - 新写入全部用新密钥,老数据在业务读取时自动解密+用新密钥重加密(lazy migration),注意控制并发更新冲突
- 千万别用“全量导出→解密→重加密→导入”这种运维操作,容易丢数据、锁表、超时
最常被忽略的一点:密钥本身也需要版本管理,而不是只靠环境变量名区分;密钥泄漏后,光换密钥不够,还得评估哪些密文可能已被解密,是否要强制用户重置敏感信息。


















