AES_ENCRYPT不是“填个密钥就能用”的函数,直接AES_ENCRYPT('123','key')大概率存无效二进制、查NULL或乱码,根源在于密钥长度、加密模式、字段类型、字符集四者未对齐;密钥须用UNHEX(SHA2('phrase',256))生成固定32字节二进制,CBC模式必须配16字节二进制IV,加密结果需HEX编码后存VARCHAR,解密前须UNHEX还原,且明文应先CONVERT(USING binary)避免隐式转换。

AES_ENCRYPT 不是“填个密钥就能用”的函数,直接 AES_ENCRYPT('123', 'key') 很大概率存进去的是无效二进制,查出来是 NULL 或乱码——问题不在语法,而在密钥长度、模式、字段类型、字符集四者没对齐。
密钥长度和模式必须显式对齐
MySQL 会静默处理不合规的密钥:'mykey'(5 字节)会被补零成 16 字节,但如果你在另一处用 UNHEX(SHA2('mykey', 256)) 生成 32 字节密钥,加解密就必然失败。ECB 模式虽无需 IV,但已被视为不安全;CBC 等模式必须传入 16 字节二进制 IV,且加密与解密必须用完全相同的 IV。
-
AES_ENCRYPT(str, key_str, 'AES-256-CBC', iv)中的iv必须是 16 字节二进制值,例如UNHEX('000102030405060708090a0b0c0d0e0f'),不能是字符串'00010203...' - 密钥建议统一用
UNHEX(SHA2('your_secret_phrase', 256))生成固定 32 字节二进制,避免手输长度失控 - MySQL 8.0.31+ 默认禁用非确定性加密,若报错
ERROR 3719,需先执行SET block_encryption_mode = 'aes-256-cbc';
加密结果必须 HEX 编码后存 VARCHAR
直接把 AES_ENCRYPT() 的二进制输出插入 VARCHAR 字段,会因字符集(如 utf8mb4)隐式转换被截断或损坏。这不是数据丢了,而是 MySQL 把二进制当文本转义了。
- 建表时字段用
VARCHAR(512)或TEXT即可,别硬上VARBINARY——除非你全程自己管理二进制流 - 插入必须包装
HEX(AES_ENCRYPT(...)),例如:INSERT INTO user (name, phone_enc) VALUES ('Alice', HEX(AES_ENCRYPT('138****1234', @key, 'AES-256-CBC', @iv))) - 查询解密时,先
UNHEX(phone_enc)还原二进制,再喂给AES_DECRYPT()
AES_DECRYPT 返回 NULL 的真实原因
AES_DECRYPT 返回 NULL 不代表“解密失败”,它只表示“输入非法”:密钥/IV 类型不对、长度不匹配、模式不一致,甚至原文含非法 UTF-8 字节都会触发。它不会告诉你哪错了。
- 排查时先在 MySQL 里复现:用
LENGTH(@key)和HEX(@key)确认密钥二进制内容完全一致,不是看起来一样 - 如果用了 IV,检查是否漏了
UNHEX()——直接写'1234567890123456'是字符串,不是二进制 - 解密后若要转字符串,外层加
CONVERT(AES_DECRYPT(...) USING utf8mb4),但前提是原始明文确实是合法 UTF-8;否则会截断或报错
带盐加密必须盐值随记录存储
MySQL 没有内置带盐 AES,靠拼接模拟。关键点是盐不能全局复用,也不能丢——它必须和密文一起落库,且每次加密独立生成。
- 插入前生成盐:
SET @salt = SUBSTR(SHA2(RAND(), 256), 1, 16); - 构造动态密钥:
CONCAT(@static_key, @salt),再加密明文 - 字段设计推荐拆成两列:
salt VARCHAR(32)+data_enc VARCHAR(512),避免 JSON 解析开销 - 解密时先查出 salt,拼出相同密钥,再执行
AES_DECRYPT(UNHEX(data_enc), CONCAT(@static_key, salt), ...)
最易被忽略的点:加密前对明文做 CONVERT(str USING binary),否则 utf8mb4 字段里存的中文可能因隐式转换丢字节;还有密钥绝对不能硬编码在 SQL 里,生产环境必须从环境变量或 KMS 注入。


















