mysql中的加密函数与数据保护

3次阅读

mysql无真正加密函数,仅有单向哈希(如md5、sha1)和aes加解密;密码应由应用层用bcrypt等处理,aes需严格匹配模式、密钥、iv且密钥须为二进制。

mysql中的加密函数与数据保护

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 脚本里,或者靠人工维护,一出故障就全量数据无法还原。

text=ZqhQzanResources