跳转到主内容
极星编程网:以代码为星,赴技术山海!

如何保护SQL敏感字段_使用加密存储防止注入直接泄露

绝大多数业务场景应选对称加密(如AES-256-GCM),因其支持WHERE查询、ORDER BY和JOIN;非对称加密因密文不可比较、无法索引,仅适合加密对称密钥本身。 SQL字段加密该用对称还是非对称 绝大多数业务场景下,选对称加密(如
AES-256-GCM
),不是因为“更简单”,而是因为要支持 WHERE 查询、ORDER BY、JOIN —— 非对称加密(如 RSA)密文长度固定且不可比较,查
user_id
或
phone
时根本没法加索引,性能直接崩。 常见错误是拿
RSA_encrypt()
去加密手机号存进数据库,结果发现连
SELECT * FROM users WHERE encrypted_phone = ?
都做不到:每次加密结果不同,没法等值匹配。 对称加密必须保管好密钥 —— 密钥不能硬编码在代码里,也不能存在数据库同一张表甚至同一个实例中 推荐用环境变量 + KMS(如 AWS KMS / 阿里云 KMS)动态解密密钥,应用启动时不加载明文密钥到内存 如果真要用非对称,只适合加密「极少量高敏元数据」,比如加密另一个对称密钥本身(即密钥封装) MySQL/PostgreSQL 里怎么安全加解密字段 别依赖
AES_ENCRYPT()
或
pgp_sym_encrypt()
直接裸用。它们默认不带认证,攻击者可篡改密文触发填充预言攻击(padding oracle),尤其在 Web 应用返回不同错误提示时风险极高。 PostgreSQL 推荐用
pgcrypto
的
encrypt_aes128()
+
decrypt_aes128()
(需手动补 IV 和 HMAC),MySQL 8.0+ 更稳妥的做法是应用层加解密,数据库只存
ciphertext
、
iv
、
hmac
三个字段,而不是用内置函数一步到位。
AES_ENCRYPT()
在 MySQL 5.7 中默认用 ECB 模式(已淘汰),且不校验完整性,禁用 若坚持用数据库函数,MySQL 8.0+ 必须显式指定
AES_ENCRYPT(data, key, 'aes-256-gcm', iv, aad)
,并自行验证
auth_tag
IV 必须随机生成、每次不同,且和密文一起存;不能复用 IV,否则相同明文产生相同密文,等于没加密 加密后还能搜手机号或身份证号吗 不能直接搜,但可以做确定性加密(Deterministic Encryption)或带盐哈希 + 辅助索引。关键点在于:你接受多大程度的隐私降级。 比如搜手机号,可对原始号码先做
HMAC-SHA256(phone, salt_from_config)
得到固定长度指纹,存在
phone_hash
字段并建索引;查询时同样算哈希再比对。这能防拖库后反查,但无法防字典穷举(因手机号空间小)。 确定性加密(如 AES-SIV)允许等值查询,但泄露「相同值是否重复」—— 如果两个用户填了同一个邮箱,密文就一样 完全不想泄露重复性?只能用搜索加密(Searchable Encryption),但目前无成熟开源方案支持高并发 OLTP 场景 身份证号建议拆:前6位(地址码)明文 + 后8位(出生日期)确定性加密 + 最后4位(顺序码+校验码)用随机盐哈希,兼顾可查与抗推测 ORM 层怎么插进加解密逻辑不污染业务代码 别在每个
save()
里手写
encrypt()
调用。用 ORM 的钩子机制(如 Django 的
pre_save
、SQLAlchemy 的
@validates
或
AttributeEvents
)自动拦截敏感字段赋值与读取。 重点不是“怎么加”,而是“怎么避免漏加”。最容易踩的坑是:JSON 字段、TEXT 字段、历史迁移数据、批量导入脚本 —— 这些地方往往绕过 ORM,直接走
INSERT INTO ... VALUES (...)
,导致新加密逻辑完全不生效。 Django 可封装
EncryptedCharField
,重写
to_python()
和
get_prep_value()
,确保所有路径统一走加解密 SQLAlchemy 推荐用
TypeDecorator
包装
LargeBinary
,把加密逻辑下沉到底层类型,连 raw SQL 插入都无效(除非绕过 ORM 直连) 务必在测试里覆盖「通过
cursor.execute()
写入数据后,ORM 读出来是否仍可解密」这个 case 最麻烦的其实是密钥轮换 —— 加密字段一旦上线,旧密文永远存在库里。新密钥加密的数据和老密钥加密的数据得共存,解密逻辑得带密钥版本标识,这点几乎没人一开始就想清楚。

相关文章