哈希函数与消息认证码:SHA、HMAC、数字签名概述
说实话,哈希函数是密码学里最容易被低估的工具。很多人觉得它不就是个“指纹”吗?嗯……它确实是,但指纹也有讲究。你得知道哪种指纹靠谱,哪种指纹一搓就掉。
这一节我们聚焦三件事:哈希函数本身(SHA 族)、消息认证码(HMAC)、数字签名。它们环环相扣——哈希负责压缩,HMAC 负责共享密钥下的完整性,数字签名则在公钥体系里加上了不可否认性。
1. 哈希函数:单向压缩的基石
哈希函数接收任意长度的输入,输出固定长度的摘要。它的核心性质我列个表:
| 性质 | 含义 | 为什么重要 |
|---|---|---|
| 单向性 | 给定 y = H(x),求 x 不可行 | 密码存储、数字签名的基础 |
| 抗碰撞性 | 找不到 x ≠ x' 使得 H(x)=H(x') | 防止伪造摘要 |
| 抗第二原像 | 给定 x,找不到 x'≠x 使 H(x)=H(x') | 防止替换消息 |
| 雪崩效应 | 输入改 1 bit,输出一半 bit 翻转 | 保证差分攻击难度 |
我个人习惯把哈希函数比作“碎纸机”。你扔进去一份文件,出来一堆碎屑。从碎屑还原文件几乎不可能,但如果两份文件碎出来完全相同的碎屑,那它们原文件一定一样——碰撞就是这个意思。
2. SHA 家族:你该选哪个?
SHA 系列从 SHA-0 走到 SHA-3,中间踩的坑不少。我在一个老旧系统中见过还在用 SHA-1 的代码,那个项目后来因为碰撞攻击被迫全部升级。那次教训让我养成了习惯:新项目一律 SHA-256 起步。
当前主流选择:
- SHA-256/384/512(SHA-2 族):安全、硬件支持好,绝大多数场景首选。
- SHA-3:基于 Keccak 算法,结构不同,未来验证性更好。如果你的系统对量子计算有远期担忧,可以提前用 SHA-3 做储备。
- SHA-1:已废弃。别用。真的别用。
3. HMAC:给哈希加把钥匙
纯粹哈希无法防止篡改——攻击者可以同时改消息和哈希值。HMAC 用共享密钥解决了这个问题:HMAC(K, m) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || m ) )。
公式看着复杂,但你可以理解为“用密钥把哈希包了两层”。我曾经在物联网设备里实现过一个最小化 HMAC,当时密钥直接从熵池取,结果发现密钥太短导致碰撞概率上升。后来改成了至少 32 字节密钥,才消停。
HMAC 的应用场景:
- API 请求签名(比如 AWS Signature V4)
- 消息完整性校验(TLS 的 MAC 层)
- 密钥派生(HKDF 就是基于 HMAC)
4. 数字签名概述:从对称到非对称
HMAC 要求双方共享同一个密钥。如果你要给全球的用户验证消息来源,共享密钥就不可行——你没法安全地把密钥发给每个人。数字签名用公钥加密体系解决了这个问题。
数字签名 vs HMAC:
| 对比维度 | HMAC | 数字签名 |
|---|---|---|
| 密钥管理 | 对称(共享密钥) | 非对称(私钥签名,公钥验签) |
| 不可否认性 | ❌ 双方都能伪造 | ✅ 只有持私钥者能签名 |
| 性能 | 快(对称加密/哈希) | 慢(公钥运算) |
| 典型算法 | HMAC-SHA256 | RSA-PSS, ECDSA, EdDSA |
实际流程很简单:签名方 对消息哈希后,用私钥加密哈希得到签名;验签方 用公钥解密签名得到哈希,再与本地计算的消息哈希做比较。你想想看,如果两个哈希一致,那说明消息没被改,且签名确实来自私钥持有者——这就是不可否认性。
5. 三者关系图谱
我用一张 SVG 把哈希、HMAC、数字签名的串联关系画出来。你一眼就能看懂它们怎么配合。
6. 避坑指南
这些年看我踩过的坑,你要是能避开,至少省两晚上调试时间:
- 哈希不等于加密。哈希不可逆,加密可逆。有人把 SHA256 当加密用,结果“解密”时懵了——这是原则性错误。
- HMAC 的密钥长度不能小于哈希输出长度。我用 HMAC-SHA256 时最少给 32 字节。短了会降低安全性。
- 数字签名的哈希算法要跟签名算法匹配。比如 RSA 签名建议用 SHA-256 而不是 SHA-1,否则签名虽然能用,但攻击者可能碰撞哈希绕过验签。
- 不要自己拼签名方案。我曾经试着“手动”组合 RSA + SHA256,结果忘了加 padding,导致存在特定攻击。请直接用标准接口,比如 RSA-PSS 或 ECDSA。
好了,哈希、HMAC、数字签名这三个概念你心里应该有数了。它们之间的关系就像:哈希是“指纹仪”,HMAC 是“验指纹的保安”,数字签名则是“公证处的公章”——各有各的活,但都离不开那个指纹。