单向散列与HMAC:底层逻辑、碰撞漏洞与长度扩展攻击
单向散列与HMAC:底层逻辑、碰撞漏洞与长度扩展攻击
在进入复杂的加解密算法之前,我们首先要面对密码学中最基础、也是日常开发和安全测试中最常打交道的组件:散列函数(Hash Function)。
无论是下载 ISO 镜像时的 SHA-256 校验、Linux /etc/shadow 文件中的密码存储,还是 Web API 接口的防篡改签名,都离不开散列函数。本文将结合日常命令行操作、真实的 Web API 场景,深入剖析 Hash 的底层逻辑、MD5 的碰撞缺陷,以及一种极其隐蔽的高级攻击手法——长度扩展攻击(Length Extension Attack)。
1. 散列函数(Hash)的底层属性与日常体现
散列函数是一种**将任意长度的输入(明文),通过压缩与混淆算法,映射为固定长度的输出(散列值/摘要)**的单向数学函数。
1.1 密码学 Hash 的三大铁律
要成为合格的“密码学级”散列函数,必须满足以下三大特性:
- 单向性(不可逆):给定散列值
H,在计算上不可能反推出原始输入M。 - 抗碰撞性(Collision Resistance):
- 弱抗碰撞:给定输入
M1,很难找到另一个输入M2,使得Hash(M1) == Hash(M2)。 - 强抗碰撞:很难找到任意两个不同的输入
M1和M2,使得它们的散列值相同。
- 弱抗碰撞:给定输入
- 雪崩效应(Avalanche Effect):输入
M哪怕只发生了一个比特(Bit)的改变,输出的散列值也会面目全非。
💻 日常接触:命令行下的雪崩效应验证 我们可以在 Linux 终端中直观地看到雪崩效应。仅仅改变了一个字母的大小写(a -> A),生成的 SHA-256 散列值完全不同,找不到任何相似的规律:
2. 从 MD5 到 SHA-3:演进与碰撞漏洞
2.1 为什么 MD5 和 SHA-1 被判了死刑?
MD5(产生 128 位输出)和 SHA-1(产生 160 位输出)曾统治了互联网几十年。但如今在任何安全审计报告中,使用它们都会被标记为高危漏洞。原因在于它们的抗碰撞性被彻底击破。
碰撞攻击(Collision Attack)的实战意义: 王小云院士团队在 2004 年破解了 MD5 的强抗碰撞性。这意味着,攻击者可以构造出两个内容完全不同,但 MD5 值一模一样的文件。
- 伪造软件签名:黑客可以构造一个正常的安装包(M1)和一个植入了木马的安装包(M2),使得两者的 MD5 完全相同。黑客将正常包提交给杀毒软件检测并获取“安全认证”签名,随后用木马包替换发布,用户下载后校验 MD5 发现完全一致,从而中招。
- 伪造数字证书:2008 年,黑客利用 MD5 碰撞,成功伪造了合法的根证书,从而可以签发任意域名的假证书,实施中间人攻击。
2.2 现代安全标准:SHA-2 与 SHA-3
- SHA-2 家族(如 SHA-256、SHA-512):目前应用最广泛的工业标准。比特币的底层工作量证明(PoW)采用的就是
SHA-256(SHA-256(Block_Header))。 - SHA-3 家族(如 Keccak 算法):虽然 SHA-2 目前依然安全,但为了防患于未然(因为 SHA-2 在结构上依然沿用了类似 MD5 的 Merkle-Damgård 结构),NIST 选定了底层数学结构完全不同的 Keccak 作为 SHA-3 标准(以太坊采用的就是 Keccak-256)。
3. 复杂环境下的完整性挑战:MAC 与 HMAC
3.1 纯 Hash 的致命缺陷
假设在云环境或微服务架构中,服务 A 要通过不安全的公网向服务 B 发送数据 Data,为了防止篡改,A 附带了 Hash(Data)。
中间人攻击:黑客截获了数据,不仅篡改了数据为 Fake_Data,还顺手重新计算了 Hash(Fake_Data) 并替换了原来的 Hash 值。服务 B 收到后校验完美通过,单纯的 Hash 彻底失效。
3.2 引入密钥的认证码:HMAC
为了解决上述问题,我们需要一种既能验证完整性,又能验证身份的机制,这就是 MAC(Message Authentication Code,消息认证码)。最常用的实现是 HMAC。
- 底层逻辑:通信双方(服务 A 和 B)提前共享一个绝对保密的密钥
Key。 - 计算公式:粗略理解为
Hash(Key + Data)。(实际的 HMAC 算法内部包含两次 Hash 运算和两次密钥填充异或,以抵御各种密码学攻击)。 - 安全效果:黑客即使截获了数据,可以篡改
Data,但他不知道Key,所以无法计算出正确的 HMAC 值。服务 B 收到后,用自己的Key重新计算 HMAC,发现不匹配,直接丢弃。
💻 实战场景:AWS/阿里云 API 的请求签名 在日常开发中调用云厂商的 API(如 OSS 对象存储、ECS 管理接口)时,都会要求在 HTTP Header 中携带一个
Signature。这个签名的底层就是 HMAC。 云厂商会给你颁发一对凭证:AccessKeyId(公开身份)和AccessKeySecret(保密密钥)。 客户端在发送请求前,使用AccessKeySecret对请求的 URL、参数、时间戳等内容进行 HMAC-SHA256 计算,得到的结果就是签名。云端收到请求后用相同的 Secret 验签,既防篡改,又防伪造。
4. 高级威胁:哈希长度扩展攻击 (Length Extension Attack)
这是针对弱 Hash 签名机制(未采用标准 HMAC)的一种极其经典且隐蔽的攻击手法,常见于安全意识薄弱的 Web API 开发中。
4.1 脆弱的 API 设计
很多没有密码学基础的开发者,为了实现类似 HMAC 的功能,自己“发明”了一种签名算法:
Signature = MD5(SecretKey + Data)
假设 Web 应用有一个文件下载接口,URL 如下:
http://api.com/download?file=report.pdf&sig=8a9f...
服务端收到请求后,会计算 MD5(SecretKey + "report.pdf"),比对 sig 是否正确。
4.2 长度扩展攻击原理
MD5、SHA-1 和 SHA-256 的底层都采用了一种名为 Merkle-Damgård 结构的迭代算法。 它的计算过程是:
- 将输入数据进行分组(如每 512 bit 一组),并在末尾进行特定的 Padding(填充)。
- 设置一个初始的内部状态(IV)。
- 用内部状态处理第一个分组,产生新的内部状态;再用新状态处理第二个分组……以此类推。
- 最后一次输出的内部状态,就是最终的 Hash 值。
致命漏洞:
对于 MD5(SecretKey + "report.pdf"),黑客虽然不知道 SecretKey 是什么,但他知道这个 MD5 的最终输出值。
在 Merkle-Damgård 结构中,这个最终输出值,其实就等于算法处理完当前所有数据后的“内部状态”!
攻击者可以直接将截获的这个 MD5 值(即状态),作为自己本地 MD5 算法的初始状态,然后向其追加任意恶意数据(如 &file=passwd),继续计算下去,从而得到一个合法的、新的 MD5 签名!
4.3 攻击复现
黑客无需知道 SecretKey,只需知道 SecretKey 的大致长度,即可构造出如下恶意请求:
http://api.com/download?file=report.pdf%80%00...[Padding]...&file=passwd&sig=New_Malicious_Hash
服务端收到这个包含了畸形 Padding 字符的请求后,执行 MD5(SecretKey + "report.pdf" + Padding + "&file=passwd"),发现算出来的 Hash 竟然与黑客提供的 New_Malicious_Hash 完全一致!校验通过,黑客成功下载了 passwd 文件。
4.4 防御方案
- 绝对不要使用
Hash(Key + Data)的方式拼接签名。 - 强制使用标准 HMAC(如
HMAC-SHA256)。HMAC 的内部结构(两次嵌套 Hash 和异或掩码)在数学上被证明对长度扩展攻击是绝对免疫的。 - 升级到 SHA-3(Keccak 算法采用了海绵结构,天然免疫长度扩展攻击)。
5. 总结
散列函数是密码学的“指纹”提取器。从 MD5 的碰撞陨落,到 SHA-2 的广泛应用,我们见证了算力与算法的博弈。 在现代复杂的微服务与云原生网络中,单独的 Hash 无法抵御中间人篡改,必须引入基于共享密钥的 HMAC 机制。同时,开发者必须敬畏密码学底层原理,避免“土法炼钢”式拼接签名,否则极其容易倒在“长度扩展攻击”等高级数学攻击的枪口下。
下一篇预告: 解决了数据的完整性,我们接下来要面对机密性。在【密码学基础】的第二篇中,我们将深入对称加密体系,揭开 AES 算法的神秘面纱,并剖析 ECB/CBC 模式的差异以及著名的 Padding Oracle(填充神谕)攻击!