aes_encrypt和aes_decrypt需配合varbinary/blob字段类型、密文预计算索引查询、kms动态密钥管理三要素,否则易致截断、全表扫描或密钥泄露。

AES_ENCRYPT 和 AES_DECRYPT 是 MySQL 字段级加密最直接可用的方案,但直接套用函数容易出错——比如字段类型不匹配、查询性能骤降、密钥硬编码泄露,甚至解密后乱码。别急着写 SQL,先看清这三件事。
加密字段必须用 VARBINARY 或 BLOB 类型,不能用 VARCHAR
MySQL 的 AES_ENCRYPT 返回的是二进制数据,不是字符串。如果目标字段是 VARCHAR,插入时会触发隐式转换,轻则截断,重则报错 Incorrect string value。
- 正确做法:建表时把加密字段定义为
VARBINARY(255)(长度按明文最大长度+16字节预留)或BLOB - 偷懒但危险的做法:用
HEX(AES_ENCRYPT(...))存进VARCHAR,读取时必须配对用UNHEX(),否则AES_DECRYPT会返回NULL - 绝对别用
latin1字符集“骗过”VARCHAR—— 编码不一致时,跨客户端或备份恢复极易丢数据
WHERE 条件里查加密字段,性能和写法都得小心
想按身份证号查用户?明文比较(AES_DECRYPT(...) = 'xxx')会让 MySQL 无法走索引,全表扫描;密文比较(idcard = HEX(AES_ENCRYPT('xxx', key)))虽能索引,但每次查询都要重复加密,且 key 必须完全一致(包括空格、大小写)。
- 推荐组合:用密文比较 + 在应用层预计算密文值,避免 SQL 中实时调用
AES_ENCRYPT - 如果必须模糊查询(如查手机号前缀),只能解密后用
LEFT(AES_DECRYPT(...), 3),但务必加WHERE前置条件缩小范围,否则性能崩塌 -
AES_DECRYPT返回NULL当密钥错误或数据损坏,所以WHERE条件里别直接比等号,先用IS NOT NULL过滤
密钥不能写死在 SQL 或配置文件里
所有示例里出现的 'my_secret_key' 都是危险信号。生产环境一旦密钥泄露,等于全库敏感数据裸奔。
- 密钥应由 KMS(如 AWS KMS、阿里云 KMS)动态获取,或从环境变量注入,严禁出现在 SQL 文件、PHP/Python 脚本明文里
- 不同环境(dev/staging/prod)必须用不同密钥,避免测试库密钥泄露波及生产
- 轮换密钥时,旧数据需离线迁移解密再重加密,不能只改新写入逻辑——否则老数据永远无法读取
AES_DECRYPT 返回二进制,CONVERT(... USING utf8mb4) 才能安全转成可读字符串)。这些细节漏掉一个,上线后就是线上事故。











