md5不能用于密码存储,因其非加密函数、无盐、易碰撞且已被证实不安全;应使用应用层专用密码哈希算法如bcrypt或argon2,并确保盐值随机、轮数可调、日志脱敏。

MD5不是加密,是哈希,且已彻底不安全
直接说结论:MD5() 函数根本不能用于密码存储——它既不是加密函数,也不具备现代密码哈希应有的防护能力。MySQL 8.0+ 已将其标记为“不安全哈希”,部分启用 VALIDATE_PASSWORD 插件的实例会直接拒绝执行含 MD5() 的 CREATE USER 语句。
常见错误现象:开发看到 MD5('123456') 输出固定 32 位字符串,就以为“加密了、不可读了”,结果拖库后,用公开网站(如 md5.cn)几秒就能反查出明文;更糟的是,攻击者根本不用解密,直接拿哈希值去彩虹表里查,大量弱密码当场命中。
-
MD5()是确定性函数:相同输入永远输出相同结果,毫无随机性 - 无盐(salt):无法抵御预计算攻击,加固定盐(如
MD5(CONCAT('abc', password)))只是拖延时间,一旦盐泄露,全量密码可批量破解 - 碰撞漏洞已被实证:2005 年起即可构造不同输入产生相同 MD5 值,登录绕过风险真实存在
SHA2() 比 MD5 强,但仍在数据库层做哈希是次优选择
如果非要从 MySQL 内置函数里选一个,SHA2(password, 256) 确实比 MD5() 更难暴力破解,但它依然不是密码哈希的正确解法。
关键问题在于:MySQL 的 SHA2() 不自动加盐,也不控制计算轮数。你得自己拼接盐(比如 SHA2(CONCAT(salt_col, password), 256)),而盐若存字段里,就得额外读一次行;若用固定盐,又回到单点泄露即全盘崩溃的老路。
- 字段长度必须设为
VARCHAR(64)或更长,否则截断导致校验失败 -
WHERE password = SHA2(?, 256)无法走索引——因为函数作用于查询参数,MySQL 无法利用 password 字段上的索引 - 不支持自适应工作因子(work factor):无法随硬件升级动态加慢计算速度,抗暴力能力停滞不前
真正安全的密码存储必须在应用层完成
数据库只负责存,不负责算。密码哈希必须由应用代码完成,使用专为密码设计的算法,例如 PHP 的 password_hash()、Python 的 bcrypt 或 passlib、Java 的 BCryptPasswordEncoder。
这类函数默认完成三件事:生成随机盐、多次迭代哈希、编码结果(含盐和轮数)。验证时只需传入原文和存储的完整哈希串,password_verify() 或对应库会自动提取参数并重算比对。
- 盐值随每个用户唯一,且不单独存字段——它已编码进最终字符串(如
$2y$10$...开头) - 轮数可调:当前推荐 bcrypt 至少 12 轮,Argon2i 需配内存与时间参数,防 GPU/ASIC 暴力
- 密码字段类型建议
VARCHAR(255),足够容纳所有主流算法输出,不留截断风险
最容易被忽略的细节:别让哈希值进日志或监控
哪怕用了 bcrypt,如果登录接口把原始密码或哈希值打到日志里,或者监控系统采集了含 password=xxx 的 SQL 查询,等于白做。
生产环境必须检查:
- 所有日志配置是否过滤了
password、pwd、credential等关键词 - ORM 或 SQL 构建层是否会在异常堆栈中暴露带参 SQL(尤其
WHERE password = ?后的绑定值) - 数据库审计日志是否开启,并排除含哈希值的 DML 记录
安全不是加个哈希函数就结束了,而是从输入、计算、存储、查询、日志、审计,每一环都得绷着一根弦。











