aes_encrypt默认ecb模式不安全,必须显式指定cbc等安全模式、16字节iv及固定长度二进制密钥,字段用varbinary存储,密钥严禁硬编码且需规范轮换。

MySQL 里 AES_ENCRYPT 怎么用才不翻车
直接说结论:AES_ENCRYPT 不是“开箱即用”的安全方案,它默认用 ECB 模式、不带 IV、不自动加盐,密钥长度没校验,用错一步就等于裸奔。别把它当 Python 的 fernet 或 Node.js 的 crypto.createCipheriv 那样用。
常见错误现象:AES_ENCRYPT('123', 'key') 返回值每次一样;字段查出来是乱码或 NULL;换服务器后解不出;应用层传参时密钥被截断(比如含空格或特殊字符)。
- 必须显式指定密钥长度:128/192/256 位(对应 16/24/32 字节),
AES_ENCRYPT会静默截断或补零,不报错 - 推荐用
AES_ENCRYPT(str, key_str, iv)形式(MySQL 5.7.4+),但iv必须是 16 字节二进制值,不能是字符串 - 密钥建议用
UNHEX(SHA2('your_secret_phrase', 256))生成固定长度二进制,避免手输密钥长度失控 - 字段类型必须设为
VARBINARY(255)或更长(AES 加密后长度 ≈ 原文长度 + 补齐字节),别用VARCHAR存加密结果
为什么 AES_DECRYPT 经常返回 NULL
AES_DECRYPT 返回 NULL 不代表解密失败,只代表“输入非法”——它不会告诉你哪错了。最常见原因是密钥或 IV 类型/长度不对,或者加密时用了不同模式(比如服务端用 ECB,你本地试 CBC)。
使用场景:排查线上解密失败时,别急着改应用代码,先在 MySQL 里复现:
- 确认加密和解密用的密钥完全一致(用
LENGTH(key_str)和HEX(key_str)对比二进制内容) - 如果用了 IV,检查是否用
UNHEX('...')转成二进制传入,而不是直接写'1234567890123456' - MySQL 8.0.31+ 默认禁用非确定性加密函数,若报错
ERROR 3719,需临时设置SET block_encryption_mode = 'aes-128-cbc'; - 注意字符集:如果原文是 utf8mb4,加密前最好
CONVERT(str USING binary),否则可能因隐式转换丢数据
加密手机号/身份证号这种定长字段要注意什么
这类字段看似简单,实则最容易暴露模式。比如所有手机号都以 1[3-9] 开头,ECB 模式下前几个块加密结果总是一样,攻击者能靠频率分析反推明文。
- 绝对不要用默认 ECB 模式(MySQL 旧版本默认),强制指定
block_encryption_mode为aes-128-cbc或aes-256-gcm(8.0.17+) - CBC 模式下,每次加密必须用新 IV,并把 IV 和密文一起存(比如 IV 存前 16 字节,密文接后面)
- 身份证号含校验位,加密后无法校验,业务上要提前做合法性判断,别等解密完才发现是错的
- 索引失效:加密字段无法走普通 B-tree 索引,模糊查询、范围查询基本废掉,得靠应用层解密后处理
和应用层协同时,密钥管理最容易被忽略的点
数据库里硬编码密钥(比如写死在触发器或视图里)等于把钥匙贴在保险柜上。但全扔到环境变量也不行——MySQL 本身不读环境变量,还得靠应用层传进来。
- 密钥绝不出现 SQL 文本中:禁止
WHERE encrypted_col = AES_ENCRYPT(?, 'my_key'),应由应用层加密后传密文值 - 如果必须数据库内加解密(如审计日志脱敏),用 MySQL 的
CREATE FUNCTION封装,密钥从系统表或外部配置加载,别写死 - 密钥轮换是最大盲区:AES 加密后数据不可逆更新,换密钥=全量重加密,没预埋迁移逻辑的话,上线就卡住
- MySQL 8.0 的
VALIDATE_PASSWORD插件管不了加密密钥强度,得靠运维流程卡住——比如密钥必须含大小写字母+数字+符号,且长度 ≥32 字节
真正麻烦的从来不是调哪个函数,而是密钥怎么生成、存在哪、谁有权看、过期了怎么办。这些事数据库不管,但出问题第一个背锅的是你写的那句 AES_ENCRYPT。











