MySQL 里没有真正意义上的「加密函数」,只有单向哈希函数
很多人在
SELECT里用
ENCRYPT()、
MD5()或
SHA1()时,以为是在「加密」,其实这些全是不可逆的哈希——你无法从结果反推出原始密码。MySQL 原生不提供 AES 加密/解密以外的双向加解密能力(且
AES_ENCRYPT()要求密钥长度严格匹配,否则返回
NULL)。
常见误用场景:
用MD5('password') 存用户密码 → 一旦哈希被撞库或彩虹表破解,等同于明文泄露
试图用 ENCRYPT()(仅 Linux crypt() 支持)做跨平台存储 → Windows 下直接失效 调用
AES_DECRYPT()但没传对
iv或密钥编码格式(比如用 UTF-8 字符串当二进制密钥)→ 返回
NULL不报错,极难排查
AES_ENCRYPT / AES_DECRYPT 的正确用法和陷阱
这是 MySQL 中唯一可用的对称加解密函数族,但极易出错。关键不是“能不能用”,而是“怎么用才不会丢数据”。
AES_ENCRYPT()和
AES_DECRYPT()必须使用相同模式(默认
ecb,但推荐显式指定
cbc)、相同密钥、相同初始化向量(
iv);
iv长度必须为 16 字节 密钥不能直接传字符串:用
UNHEX('30313233343536373839616263646566') 或 CAST('my16byteskey...' AS BINARY) 确保是二进制
加密结果是 VARBINARY,存入字段前务必确认列类型足够长(
AES_ENCRYPT()输出长度 ≥ 原文长度 + 16) MySQL 5.7+ 默认关闭
block_encryption_mode系统变量,需手动设为
aes-128-cbc才支持
iv
AES_ENCRYPT('hello', UNHEX('000102030405060708090a0b0c0d0e0f'), UNHEX('101112131415161718191a1b1c1d1e1f'))
密码存储该用什么?别自己造轮子
MySQL 本身不提供 bcrypt/scrypt/argon2 支持,所以「在数据库层做安全密码哈希」这个需求,答案很明确:不该由 MySQL 做。
应用层用bcrypt.hashpw()(Python)、
crypto.pbkdf2()(Node.js)或
password_hash()(PHP)生成哈希,再存入 MySQL 的
VARCHAR(255)字段 如果非要在 SQL 层处理(如 ETL 场景),至少用
SHA2('pass', 512) + 盐值拼接,并确保盐值随机、每用户唯一、独立存储
绝对不要用 OLD_PASSWORD()(已弃用)、
PASSWORD()(5.7+ 移除)、
ENCRYPT()(仅 crypt,弱且不可移植)
敏感字段加密后查询会变慢,而且不能索引
一旦对字段用了
AES_ENCRYPT(),它就变成二进制 blob,无法走普通 B-tree 索引;想查「某个加密后的邮箱是否已存在」,只能全表扫描解密比对,性能断崖下跌。 若需模糊查询或范围查询,加密不是解法,应考虑字段级权限(
GRANT SELECT(col1, col2) ON db.tbl)或行级安全(MySQL 8.0+ 的
CREATE POLICY) 审计日志、备份导出等环节,加密字段仍以密文形式存在,意味着密钥管理必须覆盖整个数据生命周期——漏掉任意一环,保护就归零 MySQL 企业版有透明数据加密(TDE),但只加密 ibd 文件,不防应用层泄露;开源版无此功能 实际部署时,最常被忽略的是密钥轮换机制和
iv的持久化方式——它们往往硬编码在 SQL 脚本里,或者靠人工维护,一出故障就全量数据无法还原。
