哈希函数详解:MD5、SHA-256,以及为什么哈希不是加密
2026-08-10
把任意一段文字丢进哈希工具,会得到一串类似 5d41402abc4b2a76b9719d911017c592 的输出。把输入改掉一个字符,输出会变得面目全非。这个单向变换是密码存储、文件完整性校验、数字签名乃至区块链的基石,但它也是应用密码学里被误解最多的概念之一。至今仍能看到开发者用 MD5 “加密”密码,或者把哈希值当成数据保密的证明。这篇文章讲清哈希函数到底保证了什么、每种算法适合什么场景,以及那些仍在导致数据泄露的常见错误。
哈希函数到底做了什么
密码学哈希函数接收任意长度的输入,产出固定长度的输出——哈希值(也叫摘要,digest)。让它有用的核心性质有四条:
- 确定性:相同输入永远得到相同哈希。
- 雪崩效应:输入改动一个比特,输出大约一半的比特都会变化。“hello” 和 “Hello” 的哈希之间看不出任何关联。
- 单向性:拿到哈希值,实际无法反推出输入。这是它和加密最本质的区别。
- 抗碰撞性:找到两个不同输入产生相同哈希在计算上不可行。
最后一条是决定算法生死的地方。安全研究者说某个哈希函数”被攻破”,指的是可以人为构造出碰撞——而不是说它能被逆向。
用哈希生成器可以直观感受这四条性质:分别对 hello、Hello、一篇一万字的文档做哈希,输出长度完全一样,且彼此毫无相似之处。
MD5 和 SHA-1:已被攻破,但并非一无是处
MD5(128 位输出,32 个十六进制字符)发布于 1992 年,到 2008 年被彻底攻破——研究者利用它伪造了一张非法 SSL 证书。SHA-1(160 位)撑得更久,但 2017 年 Google 用 SHAttered 攻击演示了实用碰撞:两个内容不同的 PDF 文件拥有相同的 SHA-1 哈希。
“攻破”在这里有精确含义:攻击者投入足够算力,可以构造出两个哈希相同的输入。对证书和签名来说这是灾难——恶意文件可以继承良性文件的”可信”哈希。
但 MD5 和 SHA-1 始终没有退场,有时这也合理。如果你只是把哈希当校验和用——文件传输是否出错?缓存条目是否过期?——碰撞攻击根本不成立,因为没人想欺骗你,你只是要一个快速的指纹。MD5 至今仍常见于 ETag、缓存键和数据去重,正是这个原因。
原则很简单:MD5 和 SHA-1 可以用在你自己控制的完整性校验场景,绝不能用于任何存在对手的场景——签名、证书、密码存储,或者来自不可信来源的下载文件校验。
SHA-256 家族:当前的标准选择
SHA-256 属于 SHA-2 家族,产出 256 位(64 个十六进制字符)摘要,经过二十多年审视仍未被攻破。需要安全级哈希时它就是默认选项:验证下载文件、内容寻址(Git 历史上用 SHA-1,正在向 SHA-256 迁移)、API 请求签名、区块链工作量证明。
还有两个相邻概念值得了解:
- HMAC 不是一种哈希算法,而是一种构造:
HMAC(key, message)把密钥混入哈希过程。当你需要证明消息确实来自持有密钥的一方时使用——验证 Webhook 签名就是最典型的场景。对消息做普通 SHA-256 做不到这一点,因为任何人都能重新计算。 - SHA-3(Keccak)是 2015 年标准化的全新内部结构,作为 SHA-2 万一被攻破的保险。它本身很可靠,但 SHA-2 目前没有问题,没必要为了迁移而迁移。
哈希不是加密(密码存储就是证据)
加密是可逆的:密文加上密钥能还原明文。哈希在设计上就不可逆。至今仍有遗留代码库里写着”用 MD5 加密密码”——这一句话里其实有两个错误。MD5 是哈希不是加密,而且快速的通用哈希本来就不是存密码该用的哈希。
原因如下。攻击者从不逆推哈希——他们猜测。一台 GPU 设备每秒能计算数十亿次 MD5 或 SHA-256,所以一份泄露的、没加盐的 SHA-256 密码哈希库很快就会沦陷:把字典里每个单词、每个 password123 变体都哈希一遍,和数据库比对。彩虹表(预先算好的哈希到密码映射表)让这几乎是瞬间完成。
正确的密码存储用三道防线对抗:
- 加盐(salt):每个密码配一个随机值,和密码一起哈希后随哈希存储。相同密码现在产生不同哈希,彩虹表直接作废。
- 慢哈希:bcrypt、scrypt、Argon2 这类算法故意设计得很慢——调到比如每次 100 毫秒。每秒数十亿次猜测瞬间坍缩成寥寥几次。Argon2 还要求大量内存,能抵消 GPU 和 ASIC 的优势。
- 工作因子:这些算法都有一个成本参数,随硬件进步调高。
所以正确配方是:Argon2id(password, random_salt)——或者平台所限就用 bcrypt/scrypt——无论如何都不要单独用 SHA-256,哪怕它作为通用哈希再强。想直观感受什么样的密码难猜,可以把候选输进密码强度检测器;想直接生成随机强度够高的凭据,密码生成器在浏览器本地就能完成。
那些和安全无关的日常用途
不再把哈希当作纯安全工具后,你会发现它无处不在:
- 文件完整性:Linux 发行版 ISO 旁边的
SHA256SUMS文件。下载后本地哈希比对——传输错误和恶意篡改都会表现为不匹配。 - 数据去重:存储系统对每个数据块做哈希,相同哈希只存一份。
- 哈希表:每种语言字典类型的
O(1)查找,底层都是(非密码学)哈希分桶。 - 缓存失效:构建产物里的
app.a3f2c9.js就是内容哈希——文件名只在内容变化时变化,缓存策略可以放心激进。 - 提交标识:Git 的 commit ID 就是提交内容的哈希;篡改历史会让后续所有哈希全部变化。
在这些场景里,速度重要而碰撞攻击不重要,所以快速的非密码学哈希(xxHash、MurmurHash)常常比 SHA-256 更合适。
速查表
- 需要安全级摘要:SHA-256。
- 需要用共享密钥验证消息来源:HMAC-SHA-256。
- 存储密码:Argon2id(或 bcrypt/scrypt)加唯一随机盐——绝不用快速通用哈希。
- 自己控制范围内的完整性校验:MD5/SHA-1 可以接受,但 SHA-256 成本不高,还能终结争论。
- 永远不要说”用 MD5 加密”。哈希是单向的;需要还原数据就该用加密。