aes_encrypt和aes_decrypt是mysql唯一可逆对称加解密函数,但常因密钥长度非16/24/32字节、未显式指定cbc模式及iv、字段类型不匹配(如varchar存二进制)或未用hex/unhex处理而静默返回null。

AES_ENCRYPT 和 AES_DECRYPT 是 MySQL 中唯一能做可逆对称加解密的内置函数,但直接调用大概率静默返回 NULL——不是你写错了,而是密钥、模式、字段类型或二进制处理没对齐。
为什么 AES_ENCRYPT 总是返回 NULL?
这不是报错,而是 MySQL 在底层校验失败后选择沉默:
-
AES_ENCRYPT要求密钥长度必须为 16/24/32 字节(对应 AES-128/AES-192/AES-256),'mykey'只有 5 字节,MySQL 会补零或截断,但加解密时若两端密钥长度不一致,就返回NULL - 默认模式是
AES-128-ECB,不需要 IV,但 ECB 模式不安全;一旦你显式指定AES-256-CBC却没传@iv参数,也会返回NULL - 加密结果是二进制流,如果目标字段是
VARCHAR(255)+utf8mb4,直接插入会因字符集不兼容被截断或转成乱码,存进去的就是无效数据
怎么让加密字段真正可读可查?
核心绕过所有陷阱的做法:加密输出 → HEX() 编码 → 存字符串;解密输入 ← UNHEX() 还原 ← 读字符串。
- 建表时,加密字段用
VARCHAR(255)或TEXT即可,别硬上VARBINARY——它在字符集混用时极易出问题 - 插入时必须包装:
HEX(AES_ENCRYPT('plain', @key, 'AES-256-CBC', @iv)),其中@iv是 16 字节二进制值,例如UNHEX('000102030405060708090a0b0c0d0e0f') - 查询解密时,先
UNHEX()还原,再传给AES_DECRYPT():AES_DECRYPT(UNHEX(encrypted_hex_col), @key, 'AES-256-CBC', @iv) - 解密结果仍是二进制,如需显示为字符串,外层加
CONVERT(... USING utf8mb4),但仅当原始明文确实是合法 UTF-8 时才可靠
如何避免密钥硬编码和静态复用?
密钥不能写死在 SQL 里,也不能全表共用一个密钥:
- 应用层生成并传入密钥,或从环境变量 / KMS 动态获取,数据库里只存密文
- 若必须在 SQL 层模拟“每行独立密钥”,可用盐值拼接:
CONCAT(@static_key, SUBSTR(SHA2(RAND(), 256), 1, 16)),把盐存在同表另一列(如salt VARCHAR(32)) - 解密时先查出盐,拼出相同密钥,再执行
AES_DECRYPT(UNHEX(pwd_enc), CONCAT(@static_key, salt), ...) - 注意:
RAND()在单条语句中是稳定的,但在多行 INSERT 中每行都会重算,符合预期
哪些细节不处理,加密就等于没加?
这些点不显眼,但一漏整个链路就失效:
-
iv字段必须单独存,且每次加密用新值——复用 IV 会让 CBC 模式退化为 ECB 级别风险 - 不要用
MD5()或SHA1()做密码加密,它们已被证明可碰撞;密码类字段该用SHA2(..., 256)+ 盐,且永远不可逆 -
AES_ENCRYPT返回的是二进制,如果字段定义为BLOB却在客户端用文本协议读取(比如某些 ORM 默认行为),可能触发隐式转换,导致解密失败 - MySQL 社区版不支持 TDE,别指望靠配置开关自动加密整个表空间——那只是企业版功能











