宝塔拨测博客拨测技术博客
宝塔拨测

MD5 能存密码吗?不可逆不代表安全,彩虹表和加盐各挡得住什么

MD5 不能用来存密码。原因不是「算不出原文」——那是对的,但也是它唯一做对的地方。真正的问题是三件事凑在一起:它没有密钥,所以拿到哈希的人可以离线随便试;它被设计得很快,所以试起来几乎没有成本;不加盐时同一个密码永远得到同一个哈希,所以一张预先算好的表能同时命中成千上万个库。

先把工具跑一遍看清楚它在算什么。MD5 / 哈希加密工具输入一段文本,会同时给出 MD5、SHA1、SHA224、SHA256、SHA384、SHA512、SHA3、RIPEMD160 八行结果,输出可以在 Hex、Base64、Base64url、Binary 之间切换。页面写明所有计算在浏览器本地完成,内容不会上传到服务器。

输入 hello 后,八种哈希一次性给出,MD5 为 5d41402abc4b2a76b9719d911017c592

MD5 算出来的到底是什么:128 位、32 个十六进制字符

输入 hello,八行结果的长度各不相同,但每一行都是定长的:

算法(页面标签) 输出位数 Hex 字符数
MD5 128 32
SHA1 160 40
SHA224 224 56
SHA256 256 64
SHA384 384 96
SHA512 512 128
SHA3 512 128
RIPEMD160 160 40

定长是关键:不管输入是一个字母还是一部电影,MD5 永远吐 32 个字符。这也是它能当指纹用的原因——文件多大都只占一行。但定长同时意味着信息被压掉了,1 GB 的内容映射到 128 位里,理论上必然有大量不同内容落在同一个值上。

「不可逆」只说明不能解密,不等于别人猜不到

哈希没有密钥,所以没有解密这回事:给定 5d41402abc4b2a76b9719d911017c592,没有任何运算能反推出它是 hello,除非你把候选答案一个个算过去比对。

问题就在这里——攻击者根本不需要「解密」。他有一本常见密码字典,把每个词算一遍 MD5,和拖下来的库逐条比对,命中率取决于你的用户用了多烂的密码。RFC 6151 也明确写了这个区分:MD5 的原像攻击(给定哈希找原文)复杂度仍有 2^123.4,并不现实;真正被打穿的是碰撞抗性。所以密码泄露靠的是字典和预计算,不是数学破解。

彩虹表为什么专打不加盐的 MD5

彩虹表是把「密码 → 哈希」的对应关系提前算好、压缩存起来的大表,本质是用存储换时间。它的前提很要命:同一个密码必须永远得到同一个哈希。

不加盐时这个前提成立,于是:

  • 一张表可以打穿所有用 MD5 存密码的库,不需要针对谁;
  • 8 位以内的纯数字、常见英文单词、姓名拼音这类组合,命中率极高;
  • 攻击者拿到库以后是纯离线计算,你的登录限流、验证码、IP 封禁全都管不到。

加盐就是破坏这个前提:给每个用户存一段随机值,计算时把 盐 + 密码 一起哈希,盐和哈希一起存进库。这样同一个密码在不同用户那里得到完全不同的结果,预计算的表全部作废。RFC 8018 第 4.1 节给的理由很直接:盐如果是 64 位,每个密码就对应 2^64 个可能的密钥,对手无法预先算出所有可能的键,只能等拿到盐之后再算。

注意盐不需要保密,它本来就存在库里、跟着哈希一起泄露。它防的不是「知道盐」,而是「一次算好打穿所有人」。

加盐挡住了预计算,但 MD5 缺的是成本参数

这是最容易被含糊过去的一步:加盐的 MD5 仍然不是密码存储方案。

盐挡住的只是「预先算一张表复用」。攻击者拿到库和盐之后,还是可以对每个用户、每个盐,把字典里的候选密码一个个算过去。这时候决定成败的就只剩一件事:算一次要多久。

MD5 的设计目标是快,快就是它的优点,也正好是密码存储最不想要的性质。RFC 8018 第 4.2 节说明了迭代次数的意义:把计算重复 c 次,可以让密码对暴力破解和字典攻击的安全强度提高 log2(c) 位。密码哈希算法要的正是这个可调的成本——bcrypt 的 cost 参数、scrypt 的内存开销、Argon2id 的时间与内存参数,都是让「算一次」变贵,从而把离线爆破从几分钟拉到几年。

一句可以直接照做的结论:密码存储用 bcrypt、scrypt、Argon2id 或 PBKDF2;MD5 和裸 SHA256 都不用。 盐要随机、每用户独立、和哈希一起存(bcrypt 等算法会把盐打包进结果字符串里)。

碰撞:两个不同的文件,可以算出同一个 MD5

密码泄露和碰撞是两件事,别混。碰撞指的是「两个不同的输入得到同一个哈希」,它威胁的是签名、证书、下载校验这类需要抗篡改的场景,而不是「反查密码」。

但这件事已经不是理论了。公开的两个内容不同的 6144 字节 Windows 可执行文件,MD5 同为 cdc47d670159eef60916ca03a9d4a007——在这台机器上用 md5sum 直接算出来,两个文件字节不同、哈希相同。

RFC 6151 第 2.1 节记录了攻击成本的下降过程:2006 年的结果是在一台 1.6 GHz 的普通笔记本上约一分钟找到一个 MD5 碰撞;2007 年的结果声称在 2.6 GHz 的 Pentium 4 上10 秒以内。同一份文档的结论是:MD5 在需要抗碰撞的场景(例如数字签名)已经不可接受。

所以判断标准很简单:只要有人能从「哈希相同」里获利,就不要用 MD5。 数字签名、证书、软件发布校验、防篡改标记都算。

同一段文本,为什么别处算出来的 MD5 不一样

这类问题在排查时最常见,原因基本都在「输入其实不同」。实测同一台机器、同一次运行:

输入 MD5(Hex)
hello 5d41402abc4b2a76b9719d911017c592
Hello 8b1a9953c4611296a827abf8c47804d7
你好 7eca689f0d3389d9dea66ae112e5cfd7

只把首字母改成大写,32 个字符全变。中文要注意编码:你好 在这里按 UTF-8 的两个汉字 6 个字节参与计算;如果某处按 GBK 编码成 4 个字节,字节不同,结果必然不同。

输入 Hello 后 MD5 变成 8b1a9953c4611296a827abf8c47804d7,大小写敏感

排查顺序建议这样走:

  1. 末尾有没有多一个空格或换行——从网页复制粘贴最容易带上;
  2. 大小写、全角半角是否一致;
  3. 编码是不是同一个(UTF-8 / GBK / UTF-16 出来的字节数完全不同);
  4. 计算的是「字符串」还是「文件字节」——文件里的 BOM、换行符 CRLF 都会改变结果。

那 MD5 还能用在哪:只要不是对抗场景

MD5 没被淘汰,只是不能用在「有人故意跟你作对」的地方。下面这些用途仍然成立:

  • 大文件下载后的完整性比对:官网给出 MD5,你算一遍对上就说明传输没坏。前提是你能信任官网给的这串值。
  • 缓存键、去重指纹:把长 URL 或大段配置压成 32 字符当 key,碰撞概率低到不影响业务。
  • CDN / 对象存储的 ETag:不少实现直接就是文件内容的 MD5。
  • 日志与数据管道的行指纹:只为快速比对两条记录是否一样。

以下场景一律换成 SHA-256 起步,或者干脆换专用方案:

场景 该用什么
存用户密码 bcrypt / scrypt / Argon2id / PBKDF2
数字签名、证书 SHA-256 及以上
软件发布校验、防篡改 SHA-256 及以上
消息认证(带密钥) HMAC-SHA256(不是 MD5 加个密钥)

输出格式不是只有 Hex:Base64 是同一份摘要的另一种写法

工具右上角可以切输出编码。同一份 MD5,Hex 是 5d41402abc4b2a76b9719d911017c592,切到 Base64 就是 XUFAKrxLKna5cZ2REBfFkg==。这不是另一个哈希,是同一串 16 字节换个表示:16 字节按 3 字节一组展开成 4 个字符,最后补两个 =,所以是 24 个字符。

切到 Base64 后,MD5 显示为 XUFAKrxLKna5cZ2REBfFkg==,仍是同一份 16 字节摘要

Base64url 把 + 换成 -、/ 换成 _,方便放进 URL 和文件名;Binary 是给需要二进制位串的场合。三种格式之间不改变哈希值,只改变你怎么把它存下来、传出去。

库里已经是 MD5 密码了,怎么安全地换掉

不用停机、也不用逼所有用户改密码,按这个顺序做:

  1. 加新字段,存 bcrypt / Argon2id 的结果,旧 MD5 字段先留着;
  2. 登录时判断:新字段有值就用新算法校验;没有就用旧 MD5 校验;
  3. 旧校验通过的那一刻,用用户刚输入的明文算一份新哈希写进新字段,同时可以清空旧值;
  4. 观察一段时间,确认没有用户卡在旧字段上,再删掉旧列。

关键点是不要在旧 MD5 上再叠一层 MD5 或加个固定盐——那样只是把「不安全的一步」换成「不安全的两步」,攻击者拿到代码照样能算。要换就换成有成本参数的算法,让每一次尝试都变贵。

三十秒用完这个工具

打开 MD5 / 哈希加密工具,把要算的文本粘进「输入文本」框,八行结果立刻出来,右侧每行都有复制按钮;右上角「输出编码」按需要选 Hex 或 Base64。中文、emoji 都能直接算,全部在浏览器本地完成。

要验证一个下载文件的 MD5,注意工具算的是你粘贴进去的文本,不是文件本身——文件校验应该用系统的 md5sum(Linux)或 certutil -hashfile(Windows),避免复制粘贴过程中混入换行或编码转换。

本文所有输出都来自 2026-11-09 17:37 的同一次运行,截图与正文数字同源。

一句话结论

MD5 是校验工具,不是密码工具。它算得快、不可逆、定长,这三条让它在文件比对里很好用;同样这三条,让它在密码存储里不能用。要存密码,换 bcrypt、scrypt、Argon2id 或 PBKDF2,别在 MD5 上打补丁。

常见问题

MD5 能解密回原文吗?

不能。MD5 是单向摘要,输出固定 128 位(32 个十六进制字符),原文的信息被压缩掉了,没有任何密钥或逆运算能还原。网上说的「MD5 解密」都是拿常见密码算一遍再比对,本质是查表,不是解密。

MD5 加盐以后就能存密码了吗?

不够。加盐能让预计算的彩虹表失效,因为每个用户的盐不同,同一个密码算出的哈希也不同。但 MD5 本身没有成本参数,攻击者拿到盐以后仍然可以高速逐个试,盐救的是「一次查表打穿全库」,救不了「单个密码被快速爆破」。密码存储要换 bcrypt、scrypt、Argon2id 或 PBKDF2 这类带成本参数的算法。

为什么同一段文字在别处算出的 MD5 和我这里不一样?

先查三件事:空格和换行、大小写、字符编码。实测 hello 是 5d41402abc4b2a76b9719d911017c592,Hello 是 8b1a9953c4611296a827abf8c47804d7,只差首字母;中文按 UTF-8 编码算「你好」得 7eca689f0d3389d9dea66ae112e5cfd7,换成 GBK 编码字节不同,结果自然不同。

两个不同的文件 MD5 一样,是工具算错了吗?

不是。MD5 的碰撞已经被实际构造出来:两个内容不同的 6144 字节可执行文件,MD5 同为 cdc47d670159eef60916ca03a9d4a007。日常下载校验里两个文件 MD5 相同基本可以认为文件一致,但只要对方能控制文件内容,就可以故意做出 MD5 相同的不同文件,所以对抗场景不要只靠 MD5。

MD5 和 SHA256 哪个更适合存密码?

两个都不适合直接存密码,因为它们都设计得很快、没有可调的成本参数。SHA256 的好处是没有 MD5 那种已被实际构造的碰撞,用于文件校验和签名更稳,但它依然算得太快。存密码要用专门为此设计的算法:bcrypt、scrypt、Argon2id 或 PBKDF2。

现在还在用 MD5 存密码的老系统怎么改?

不要指望一次性让所有人改密码。常见做法是双字段并存:登录时先用旧的 MD5 校验,校验通过后立刻用 bcrypt 或 Argon2id 重新算一份存进新字段,下次登录优先用新字段。用户不用做任何操作,存量数据会随着登录逐步迁完。

MD5密码存储彩虹表加盐哈希哈希碰撞宝塔拨测