aes_encrypt()静默返回null主因是密钥长度非16/24/32字节、未匹配加密模式与iv、字段类型不支持二进制或字符集转换污染;安全做法是hex加密结果存varchar,解密前unhex还原,并显式指定一致的密钥、iv和模式。

AES_ENCRYPT() 不是“填个密钥就能用”的函数,直接写 AES_ENCRYPT('abc', 'key') 大概率存进去的是 NULL 或乱码——问题不在语法,而在二进制、字符集、模式、密钥长度四者没对齐。
为什么 AES_ENCRYPT 总是返回 NULL?
静默返回 NULL 是最典型的失败信号,不是报错,所以容易被忽略。根本原因是底层校验不通过:
- 密钥长度不对:
'mykey'是 5 字节,但AES_ENCRYPT要求必须为 16 / 24 / 32 字节(对应 AES-128 / AES-192 / AES-256),短了会补零、长了会截断,加解密时若长度不一致就返回NULL - 模式与参数不匹配:显式指定
'AES-256-CBC'却没传init_vector参数,或传了但长度不是 16 字节,也会返回NULL - 字段类型存不住二进制:目标列是
VARCHAR(255)+utf8mb4,而AES_ENCRYPT()输出的是原始二进制流,直接插入会被字符集转换污染,最终入库的就是损坏数据
怎么安全地存加密结果到 VARCHAR 字段?
绕开所有字符集和字段类型陷阱的唯一可靠路径是:加密 → HEX 编码 → 存字符串。
- 建表时字段用
VARCHAR(512)或TEXT即可,不用硬上VARBINARY或BLOB - 插入时必须套
HEX():HEX(AES_ENCRYPT('plain', @key, 'AES-256-CBC', @iv)) -
@iv必须是 16 字节二进制值,例如:UNHEX('000102030405060708090a0b0c0d0e0f'),且每条记录应独立生成、单独存储 - 不要复用 IV,也不要固定写死;IV 可存在同一行另一列,类型为
VARCHAR(32)(HEX 后长度为 32)
解密时为什么显示乱码或空值?
解密链路上任一环节断裂都会导致输出不可读,常见断点在编码还原和字符转换:
- 必须先用
UNHEX()把存的 HEX 字符串转回二进制,再喂给AES_DECRYPT();漏掉这步,AES_DECRYPT会把十六进制字符串当二进制处理,结果必为NULL -
AES_DECRYPT()返回仍是二进制,想看明文得显式转码:CONVERT(AES_DECRYPT(UNHEX(enc_col), @key, 'AES-256-CBC', @iv) USING utf8mb4) - 仅当原始明文确实是合法 UTF-8 时,
CONVERT(... USING utf8mb4)才安全;如果加密的是银行卡号等纯数字,直接CAST(... AS CHAR)更稳妥 - 密钥、IV、加密模式三者必须与加密时完全一致,差一个字节或一个字母,结果就是
NULL
带盐加密能不能在 SQL 层实现?
MySQL 没有内置加盐 AES 函数,但可以用字符串拼接模拟,关键在于盐必须随记录存储且不可复用:
- 插入前生成盐:
SUBSTR(SHA2(RAND(), 256), 1, 16)(取前 16 位作 16 字节 salt) - 构造动态密钥:
CONCAT(@static_key, salt_value),再用于加密 - 把 salt 和加密后的 HEX 值分别存入两列:
salt VARCHAR(32)+pwd_enc VARCHAR(512) - 解密时先查出 salt,拼出相同密钥,再执行:
AES_DECRYPT(UNHEX(pwd_enc), CONCAT(@static_key, salt), 'AES-256-CBC', @iv)
真正难的不是写这几行 SQL,而是确保密钥不硬编码、IV 不复用、salt 不共享、HEX/UNHEX 不遗漏——少一步,数据就不可逆。生产环境建议优先用应用层加密,SQL 层 AES 仅作兜底或审计场景使用。











