如何通过AES_ENCRYPT和AES_DECRYPT函数在MySQL中实现高效加解密操作?
- 内容介绍
- 文章标签
- 相关推荐
本文共计906个文字,预计阅读时间需要4分钟。
MySQL 自带的 `AES_ENCRYPT` 和 `AES_DECRYPT` 可以进行加密和解密,但它们不能用于安全存储——它们不自动加盐、不进行迭代、不管理密钥,全依赖你自己。不留意的话,安全风险很大。
为什么 AES_ENCRYPT 解出来的值总是 NULL
最常见原因是密钥长度不对或数据类型不匹配。MySQL 的 AES 实现严格遵循 NIST 标准,只接受 128/192/256 位密钥(即 16/24/32 字节的字符串),传入 16 个数字字符 '1234567890123456' 可以,但传 '12345678'(8 字节)就会静默失败,返回 NULL。
实操建议:
- 用
UNHEX()或SHA2()生成固定长度密钥,比如SHA2('my_secret_key', 256)得到 64 字符 hex 字符串,再用UNHEX(LEFT(SHA2('my_secret_key', 256), 64))截取前 32 字节作为 256 位密钥 - 加密字段必须是
BLOB、VARBINARY或能容纳二进制数据的类型;如果存进VARCHAR且没设COLLATE utf8mb4_bin,可能被截断或乱码 - 确认 MySQL 版本 ≥ 5.7.6 —— 旧版默认用 ECB 模式,无 IV,极不安全;5.7.6+ 默认用 ECB,但可通过
AES_ENCRYPT(str, key, iv)显式指定 CBC 模式(需额外传第三个参数)
AES_DECRYPT 返回空字符串而不是原始内容
不是解密失败,而是解密后结果被 MySQL 当作非 UTF-8 字符串处理,转成字符串时丢弃了不可见字节(比如开头的 \0)。典型表现:加密后查出来是乱码,AES_DECRYPT 结果长度为 0,但用 LENGTH(AES_DECRYPT(...)) 发现其实有字节数。
实操建议:
- 解密后立刻用
CONVERT(... USING utf8mb4)或CAST(... AS CHAR)强制转成可读字符串 - 更稳妥的做法是:加密前先用
TO_BASE64()编码明文,再加密;解密后再FROM_BASE64()—— 这样全程都是 ASCII 安全字符,避免二进制污染 - 别在应用层直接拼接 SQL 解密语句,尤其密钥来自用户输入——
AES_DECRYPT不防注入,恶意构造的密钥可能导致服务端报错泄露信息
用 AES_ENCRYPT 存密码?千万别
它不加盐、不慢哈希、不抗暴力穷举。哪怕你用了 32 字节密钥,攻击者拿到密文 + 知道你用的是 AES-CBC,就能用 GPU 批量跑密钥。真实业务中,密码必须用 PASSWORD()(已弃用)、SHA2() 加盐,或者更好——交给应用层用 bcrypt / argon2 处理。
它的合理用途其实是:临时保护敏感字段(如手机号、邮箱)在数据库内“不可直读”,且密钥由应用统一管理、不落地到 SQL 日志中。例如:
INSERT INTO users (name, phone_enc) VALUES ('张三', AES_ENCRYPT(TO_BASE64('13800138000'), @key));
查询时:
SELECT name, FROM_BASE64(AES_DECRYPT(phone_enc, @key)) AS phone FROM users;
IV(初始向量)若复用、或固定写死,会极大削弱 CBC 安全性;每次加密必须生成新随机 IV,并和密文一起存(比如拼在密文前面,解密时拆开),MySQL 本身不帮你管这个。
本文共计906个文字,预计阅读时间需要4分钟。
MySQL 自带的 `AES_ENCRYPT` 和 `AES_DECRYPT` 可以进行加密和解密,但它们不能用于安全存储——它们不自动加盐、不进行迭代、不管理密钥,全依赖你自己。不留意的话,安全风险很大。
为什么 AES_ENCRYPT 解出来的值总是 NULL
最常见原因是密钥长度不对或数据类型不匹配。MySQL 的 AES 实现严格遵循 NIST 标准,只接受 128/192/256 位密钥(即 16/24/32 字节的字符串),传入 16 个数字字符 '1234567890123456' 可以,但传 '12345678'(8 字节)就会静默失败,返回 NULL。
实操建议:
- 用
UNHEX()或SHA2()生成固定长度密钥,比如SHA2('my_secret_key', 256)得到 64 字符 hex 字符串,再用UNHEX(LEFT(SHA2('my_secret_key', 256), 64))截取前 32 字节作为 256 位密钥 - 加密字段必须是
BLOB、VARBINARY或能容纳二进制数据的类型;如果存进VARCHAR且没设COLLATE utf8mb4_bin,可能被截断或乱码 - 确认 MySQL 版本 ≥ 5.7.6 —— 旧版默认用 ECB 模式,无 IV,极不安全;5.7.6+ 默认用 ECB,但可通过
AES_ENCRYPT(str, key, iv)显式指定 CBC 模式(需额外传第三个参数)
AES_DECRYPT 返回空字符串而不是原始内容
不是解密失败,而是解密后结果被 MySQL 当作非 UTF-8 字符串处理,转成字符串时丢弃了不可见字节(比如开头的 \0)。典型表现:加密后查出来是乱码,AES_DECRYPT 结果长度为 0,但用 LENGTH(AES_DECRYPT(...)) 发现其实有字节数。
实操建议:
- 解密后立刻用
CONVERT(... USING utf8mb4)或CAST(... AS CHAR)强制转成可读字符串 - 更稳妥的做法是:加密前先用
TO_BASE64()编码明文,再加密;解密后再FROM_BASE64()—— 这样全程都是 ASCII 安全字符,避免二进制污染 - 别在应用层直接拼接 SQL 解密语句,尤其密钥来自用户输入——
AES_DECRYPT不防注入,恶意构造的密钥可能导致服务端报错泄露信息
用 AES_ENCRYPT 存密码?千万别
它不加盐、不慢哈希、不抗暴力穷举。哪怕你用了 32 字节密钥,攻击者拿到密文 + 知道你用的是 AES-CBC,就能用 GPU 批量跑密钥。真实业务中,密码必须用 PASSWORD()(已弃用)、SHA2() 加盐,或者更好——交给应用层用 bcrypt / argon2 处理。
它的合理用途其实是:临时保护敏感字段(如手机号、邮箱)在数据库内“不可直读”,且密钥由应用统一管理、不落地到 SQL 日志中。例如:
INSERT INTO users (name, phone_enc) VALUES ('张三', AES_ENCRYPT(TO_BASE64('13800138000'), @key));
查询时:
SELECT name, FROM_BASE64(AES_DECRYPT(phone_enc, @key)) AS phone FROM users;
IV(初始向量)若复用、或固定写死,会极大削弱 CBC 安全性;每次加密必须生成新随机 IV,并和密文一起存(比如拼在密文前面,解密时拆开),MySQL 本身不帮你管这个。

