密钥绝不能明文出现在SQL语句中,必须由应用层传入参数占位符;加密字段须用VARBINARY类型存储二进制密文;社区版不支持TDE,需应用层加密+备份加密补位;密钥须轮换并配合版本标识或KMS管理,且全生命周期需审计。

密钥绝不能出现在SQL语句里
只要密钥以明文形式写进 AES_ENCRYPT() 或 AES_DECRYPT() 的第二个参数,它就会被记录在 binlog、慢查询日志、general log、客户端命令历史,甚至可能被监控工具捕获。一旦数据库被拖库或日志泄露,密钥就等于公开。
- 错误写法:
SELECT AES_ENCRYPT('123456789', 'my-secret-key'); - 正确做法:密钥必须由应用层生成并传入,SQL 中只接收参数占位符(如 ? 或 :key)
- 密钥派生推荐用
SHA2(CONCAT(user_password, salt), 256)得到 32 字节密钥,避免静态字符串 - 若用环境变量或配置文件加载密钥,确保该文件权限为
600,且不在 Web 可访问路径下
字段类型必须匹配二进制输出
AES_ENCRYPT() 返回的是二进制数据(VARBINARY),不是字符串。用 VARCHAR 存会导致乱码、截断或隐式转换失败,查询时 AES_DECRYPT() 返回 NULL 却不报错——这是最常被忽略的静默故障点。
- 加密字段必须声明为
VARBINARY(255)或更长(AES-256-CBC 输出长度 ≈ 原始长度 + 16 字节对齐) - 不要用
HEX()包裹再存VARCHAR,虽然可行,但增加解密前的UNHEX()步骤,易漏、易错、性能略损 - 表字符集无关紧要,但字段类型必须是二进制类(
BLOB、BINARY、VARBINARY)
MySQL社区版不支持TDE,别信 innodb_encrypt_tables
社区版中设置 innodb_encrypt_tables=ON 不会真正加密数据文件,只是让配置“看起来生效”。它不会阻止攻击者直接读取 .ibd 文件获取明文——这在物理服务器或云主机快照泄露时尤为致命。
- TDE(透明数据加密)仅企业版支持,社区版无对应实现
- 字段级加密 ≠ 表空间加密,前者保护内容,后者保护存储介质;两者需配合使用才完整
- 若无法升级企业版,必须靠应用层加密 + 备份文件加密(如
gpg --symmetric)+ 文件系统级加密(如 LUKS)补位 - 检查是否真加密:用
strings /var/lib/mysql/db/table.ibd | head -20查看敏感字段明文是否存在
密钥轮换时旧数据怎么办
密钥不是设一次就一劳永逸。定期轮换是基本要求,但轮换后旧密文无法用新密钥解密,导致数据不可读——很多团队卡在这里,最终放弃轮换。
- 方案一:加字段存密钥标识(如
key_version TINYINT),每次加密附带版本号,解密时查表选对应密钥 - 方案二:用 KMS(如 HashiCorp Vault、AWS KMS)托管密钥,KMS 自动维护密钥版本和策略,应用只传密钥 ID
- 禁止“全量重加密”:批量更新表会锁表、耗资源、易中断;应结合业务低峰期分批处理 + 写双写逻辑过渡
- 备份密钥必须与数据备份同步归档,且单独加密存储——密钥丢了,备份等于废片


















