← 返回博客
语言: English 中文
Crypto 2026-05-26 10 分钟

MD5、SHA-256、HMAC 与密码哈希解决的是不同问题

密码学哈希函数是确定性的“压缩器”:任意长度输入压成定长输出。这就是它的全部职责。真正可用的哈希函数还提供若干性质——抗原像、抗第二原像、抗碰撞——这些性质让它在安全上有用。今天仍在被使用的哈希里,有一半已经丢掉了一个或多个性质。能分清谁是谁,能让你不上头条。

哈希MD5SHA-256SHA-3HMACbcryptArgon2

整章先放一句永久警告:不要用快哈希存密码。 这是这个领域最有后果的一条规则,也是大多数"我大学学过哈希"的直觉跑偏的地方。下面会讲。先讲基础。

哈希函数到底在做什么

哈希函数 H 接受任意长度输入,产出定长输出("摘要")。对密码学哈希,三条性质决定了它有没有用:

  1. 抗原像(preimage resistance)。 给定摘要 d,在计算上不可行地找到任何 x 使 H(x) = d。(哈希应当不可逆。)
  2. 抗第二原像(second-preimage resistance)。 给定 x,在计算上不可行地找到不同的 x' ≠ x 使 H(x') = H(x)。(哈希不应当被在原像下篡改。)
  3. 抗碰撞(collision resistance)。 在计算上不可行地找到任意两个不同输入 x ≠ x' 使 H(x) = H(x')。(不同输入不应共享摘要。)

抗碰撞是三者中最强的。一个被说"破了"的哈希,通常意思是抗碰撞没了。失去抗碰撞的哈希在某些用途上还能用(HMAC 仍然成立、文件指纹大多还算安全),但在另一些用途上很危险(数字签名、证书校验)。

哈希家族

MD 系。 MD5(1992)已经被全面攻破。第一次实用碰撞 2004 年由 Wang 与 Yu 公开;选择前缀攻击 2007 年跟进(2012 年 Flame 恶意软件利用了它)。MD5 输出 128 bit。只在非密码学场景用它——校验文件没有意外损坏,而不是校验它没有被人为篡改。更好的非密码学选项已经存在(CRC32、xxHash)。新代码停止使用 MD5。

SHA-1。 NIST 1995 年发布,输出 160 bit。抗碰撞已被攻破。Google 在 2017 年 2 月公开 "SHAttered"——首次已知 SHA-1 碰撞(两份 PDF 哈希相同)。2020 年的 SHAmbles 把选择前缀碰撞做到了便宜的程度。浏览器和证书机构在 2014~2017 年间陆续弃用 SHA-1。Git 还在用 SHA-1,但自 2017 年起加了碰撞检测(SHA1DC 算法)。新代码不该再用 SHA-1。

SHA-2 家族。 NIST 2001 年发布,包含 SHA-224、SHA-256、SHA-384、SHA-512,以及截断版 SHA-512/224、SHA-512/256。截至 2026 年都被认为是安全的。SHA-256 是现代主力——Bitcoin、TLS 证书、Linux 包管理器、Git 的迁移目标都用它。SHA-512 在 64 bit 硬件上有时反而比 SHA-256 快,因为它原生用 64 bit 字操作。

SHA-2 家族基于 Merkle–Damgård 构造,有一个值得知道的怪癖:长度扩展(length extension)。给定 H(secret || message)secret 的长度,你可以在不知道 secret 的情况下计算 H(secret || message || padding || extra)。这一点会打穿"把 secret 和消息拼起来哈希做认证"那种朴素方案。修复方式是 HMAC,下面讲。

SHA-3 家族。 NIST 2015 年发布,是 SHA-1 之后那场公开竞赛的胜出者。内部构造完全不同(Keccak / 海绵结构),没有长度扩展问题。SHA-3-256、SHA-3-512 是常见选择。在大多数平台软件实现上比 SHA-2 慢,但抵抗的攻击类别不同。如果 2026 年从零开始选、没有兼容性约束,SHA-3 或 BLAKE3(下一段)都是现代选择;要和现有系统对接,SHA-256 没问题。

BLAKE2 / BLAKE3。 BLAKE2(2012)是 SHA-3 决赛失利者,但更快、安全性等同。BLAKE3(2020)可并行、极快——是现代硬件上可量化最快的安全哈希。b3sum、若干包管理器、性能敏感系统在用。采纳率在持续上升。

带回家的结论:通用指纹用 SHA-256;想要现代替代品就 SHA-3 或 BLAKE3;做安全相关决策永远不用 MD5 或 SHA-1。

HMAC:把密钥和哈希正确组合的方式

如果你想做消息认证——证明这条消息来自共享密钥的拥有者——错误做法有两种:H(secret || message)(在 Merkle–Damgård 类哈希上有长度扩展漏洞);H(message || secret)(对碰撞攻击脆弱)。正确做法是 HMAC,定义在 RFC 2104:

HMAC(K, M) = H((K' XOR opad) || H((K' XOR ipad) || M))

这个双层哈希舞蹈让长度扩展失效。具体地,HMAC-SHA256(key, message) 就是消息认证你想要的东西。各语言都有:Python 的 hmac.new、Node 的 crypto.createHmac、Java 的 Mac.getInstance("HmacSHA256")

HMAC 密钥可以是任意长度;算法处理短和长的密钥。一个实际细节:超过哈希分组长度(SHA-256 是 64 字节)的密钥会先被哈希再使用,所以过长的密钥并不带来额外安全。

最大的那个混淆:密码不一样

像 SHA-256 这种通用密码学哈希是故意快的。哈希一份 1 GB 文件应该一两秒。哈希一段 16 字节的密码应该是微秒级。

这对密码存储是个问题。如果数据库泄露、攻击者拿到密码哈希,"快"意味着他们每秒能试上十亿次。现代 GPU 每秒能算约 100 亿次 SHA-256。常见密码经由 rainbow table 反查的延迟接近为零。

密码哈希函数是有意慢的、有意吃内存的、并且是参数化的——硬件变快时你可以把代价调高。当前可选项:

  • bcrypt(1999)。元老。可配置 cost factor("工作因子")。密码超过 72 字节会被静默截断。多数场景下今天仍然安全。2026 年的默认 cost:12(≈ 现代服务器单次哈希约 250ms)。
  • scrypt(2009)。引入显著的内存代价——GPU 内存稀缺,所以连 GPU 都难。可配置 N、r、p 参数。
  • Argon2(2015)。密码哈希竞赛冠军。三个变体:Argon2d(数据相关、抗 GPU)、Argon2i(数据无关、抗侧信道)、Argon2id(混合,推荐)。如果你的平台有可靠实现,今天的最佳选择。
  • PBKDF2(RFC 2898)。"还能接受的"选项里最老的。本质是迭代 HMAC。不内存敏感,所以 GPU 吃定它。只在被强制时用(FIPS 合规、遗留系统)。推荐迭代次数一直在涨——OWASP 2023 年建议 SHA-256 用 600,000 次。

今天存密码:用 Argon2id。RFC 9106 给了推荐参数——典型:64 MB 内存、3 次迭代、并行度 4。如果平台没有 Argon2,用 bcrypt(cost ≥ 12)或 scrypt。万不得已才用 PBKDF2。永远不要用裸 SHA-256 存密码。有人这么干,后面跟着一连串 CVE。

加盐(salt)

盐是每个密码独有的随机值,和哈希一起存。它的职责:阻止预计算攻击(rainbow table);让两个用同样密码的用户产生不同的哈希。

关键性质:

  • 每密码一盐,不是每系统一盐。 全站统一的盐不能阻止针对这个站的 rainbow table。
  • 随机且唯一。 用密码学随机数源,至少 16 字节。
  • 存而不藏。 盐放在数据库里、和哈希挨着即可;安全性来自它的"每用户唯一"。

现代密码哈希函数(bcrypt、scrypt、Argon2)会自动生成并嵌入盐——产出字符串里就含算法参数、盐、摘要。用这些函数时你不需要单独管理盐。

另一个独立概念:胡椒(pepper)——一个全系统级别的额外秘密,加到每个密码哈希里,存在数据库之外(比如环境变量或 HSM)。如果数据库泄露但 pepper 没泄,暴力破解就更难。这是纵深防御里的锦上添花,不是基石。现代建议是先把 Argon2 参数调对,再考虑 pepper。

文件完整性 vs 消息认证

两个表面相似但本质不同的用例:

文件完整性。 "这个文件传过来损坏了吗?" / "它和发布方公布的校验值匹配吗?"

  • 用 SHA-256。
  • 哈希值需要通过独立的可信通道分发(发布方 HTTPS 网站)。
  • 一个孤立的哈希值毫无用处——能替换文件的人也能替换哈希。

消息认证。 "这条消息真的是持有共享密钥的人产生的吗?传输过程中没有被改?"

  • 用 HMAC-SHA256(如果同时需要保密性,看 AES 那篇——用 AEAD)。
  • 密钥必须保密。H(key || message) 不能做认证;HMAC 才能。

数字签名是第三种情形——公钥认证:签名者持私钥,验证者持公钥。那不是哈希,那是 RSA-PSS / ECDSA / EdDSA,算法内部嵌哈希。

哈希碰撞与生日界

n bit 哈希,期望在 ~2^(n/2) 次随机输入后出现一次碰撞,即"生日界"。所以:

  • 128 bit 哈希(MD5):约 2^64 ≈ 18 万亿亿次后期望碰撞。过去看着安全;加上已知结构性弱点,不安全。
  • 256 bit 哈希(SHA-256):约 2^128 ≈ 3.4 × 10^38 次。不可行。
  • 512 bit 哈希:2^256 次。彻底不可行。

故意构造的碰撞,破损哈希的代价比生日界低得多。SHA-1 选择前缀碰撞(SHAmbles 2020)大约 4.5 万美元的云算力。MD5 选择前缀碰撞,笔记本上几分钟。

碰撞只是"不希望发生"的用途(文件去重、内容寻址存储),256 bit 绰绰有余。碰撞致命的用途(合同签名、特权二进制校验),用 256+ bit 没有已知弱点的哈希。

常见坑

  • 用 SHA-256(或更糟,MD5)存密码。 用 Argon2id / bcrypt / scrypt / PBKDF2。
  • H(secret || message) 做认证。 用 HMAC。
  • 多用户共用一个盐。 一密码一盐。
  • 把"全站 pepper"也写进数据库——失去意义,泄露时一起泄。
  • 校验下载用未签名的哈希。 哈希需要走经过认证的通道。
  • 比较哈希时用 == 时序攻击。用 crypto.timingSafeEqual(Node)、hmac.compare_digest(Python),任何语言里都用恒定时间比较。
  • 截断摘要到 128 bit 以下。 偶尔为节省存储这么干;抗碰撞强度退化到截断长度的一半。128 bit 是实践下限,256 bit 才舒适。
  • 以为"哈希过了"就安全。 password123 的 SHA-256 在哪个站都一样,因为没盐。是 hash + salt + 慢 KDF,不是只哈希。
  • 2026 年还用 MD5,因为它快。 用 BLAKE3——更快而且安全。
  • 哈希一段超长输入得到一个短标识,假定不会碰撞。 不可能 ≠ 不会。256 bit 的"不可能"够用;64 bit 截断的"不可能"不够。

选哪个

用途 推荐 可接受 别用
文件 / 数据指纹 SHA-256 BLAKE3、SHA-3 MD5、SHA-1
消息认证 HMAC-SHA256 HMAC-BLAKE2/3 朴素 H(key‖msg)
密码存储 Argon2id bcrypt、scrypt SHA-256、PBKDF2(最后兜底)
数字签名 ECDSA-P256 / Ed25519(内部用 SHA-2) RSA-PSS-SHA256 任何内嵌 SHA-1 的方案
非密码学校验和(CRC、去重) xxHash、CRC32 BLAKE3 MD5(如果你需要密码学完整性)
随机 ID 生成 来自 CSPRNG 的 UUIDv4 / v7 "计数器哈希"

最该内化的一条:快哈希用于数据指纹;慢哈希用于密码。 选错了,要么校验毫无意义(用慢哈希做校验),要么安全性纯粹装饰(用快哈希做密码)。

哈希里别的细节——内部构造、填充、输出截断——都是你的库帮你处理的事。需要你做的只有两个决定:(1) 选哪个哈希算法;(2) 存密码时选哪个 KDF、用什么参数。这两件事做对,剩下都不用操心。

主要参考资料

用于核对本文技术细节的标准与官方文档。

在浏览器里直接计算哈希

本站哈希工具用浏览器原生的 Web Crypto API 计算文本或文件的 MD5、SHA-1、SHA-256、SHA-384、SHA-512。适合校验下载文件、快速对比两份内容。数据不会离开浏览器。

打开哈希工具

相关文章

继续阅读同一主题领域的实践指南。

查看全部文章

Cookie 同意

我们使用 Cookie 来增强您的体验并展示相关广告。您可以自定义您的偏好。