4. 哈希与消息认证:SHA-2/SHA-3家族、HMAC构造、哈希长度与碰撞安全性、SM3国密算法

哈希函数,说白了就是数据的「数字指纹」。你给它任意长度的输入,它吐出一段固定长度的摘要。这个过程不可逆,而且理论上不同的输入应该得到不同的摘要。嗯,理论上是这样。

我在实际做安全审计时,见过不少因为哈希选型翻车的案例。有人图快用MD5做签名,结果被碰撞攻击打得满地找牙。也有人把SHA-1当宝贝供着,直到Google公开了SHAttered碰撞实例才慌忙升级。所以这一章,咱们把哈希家族、HMAC构造、以及国密SM3彻底捋一遍。

4.1 SHA-2家族:当前的主力军

SHA-2是NIST在2001年发布的标准,至今仍是互联网的基石。它包含SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224和SHA-512/256这六种变体。你想想看,为什么搞这么多长度?

核心原因就一个:适配不同的安全等级和平台。SHA-256输出32字节,适合大多数场景;SHA-512输出64字节,在64位CPU上反而比SHA-256更快——因为它的运算字长是64位,跟CPU寄存器宽度一致。我在给金融核心系统做方案时,就强制要求使用SHA-256以上,SHA-1一律禁用。

核心结论: 2025年的今天,SHA-256是安全基线,SHA-384/SHA-512用于高安全场景。SHA-1和MD5必须彻底退役。

SHA-2的内部结构

SHA-256的流程大致分三步:

  1. 填充:在消息末尾补一个'1'位,然后补'0'直到长度模512等于448,最后附加64位的原始长度值。
  2. 分块:把填充后的消息切成512位的块。
  3. 压缩:每个块经过64轮迭代,更新8个32位工作变量。

说白了就是「分块-迭代-压缩」的经典Merkle-Damgård结构。不过SHA-2有个小毛病——长度扩展攻击。什么意思?就是如果你知道H(M),即使不知道M本身,也能算出H(M || padding || extra)。这很危险,所以后面才有了HMAC和SHA-3。

4.2 SHA-3家族:新一代的胜出者

SHA-3是NIST在2015年选定的新标准,它跟SHA-2完全不是一个路数。SHA-3用的是Keccak算法,基于海绵结构(Sponge Construction),而不是Merkle-Damgård。

海绵结构的好处是:

  • 天然抵抗长度扩展攻击
  • 可以输出任意长度的摘要(可扩展输出函数,XOF)
  • 在硬件实现上更高效

SHA-3家族包括SHA3-224、SHA3-256、SHA3-384、SHA3-512,以及两个可扩展输出函数SHAKE128和SHAKE256。我在做物联网固件签名时,就特别喜欢用SHAKE128——因为它可以按需输出256位或更长的摘要,灵活性极高。

我的经验: 如果你在开发新协议,优先考虑SHA-3。虽然SHA-2目前还没被攻破,但SHA-3的设计更现代,抗量子攻击的潜力也更强。我在去年参与的一个区块链项目中,就把核心哈希从SHA-256换成了SHA3-256,主要是为了防长度扩展攻击。

4.3 哈希长度与碰撞安全性

哈希函数的安全性,核心看两个指标:抗碰撞性和抗原像性。咱们重点说碰撞。

根据生日悖论,找到碰撞的期望工作量大约是2^(n/2)次哈希计算,其中n是输出比特数。所以:

哈希算法 输出长度 碰撞安全强度 当前状态
MD5 128位 2^64(已被攻破) ❌ 彻底禁用
SHA-1 160位 2^80(已被攻破) ❌ 彻底禁用
SHA-256 256位 2^128 ✅ 安全
SHA-384 384位 2^192 ✅ 安全
SHA-512 512位 2^256 ✅ 安全
SM3 256位 2^128 ✅ 安全

你可能会问:2^128的碰撞强度够用吗?我直接说结论:以目前人类的计算能力,2^128是天文数字。就算你用上全球所有的算力,也要算到宇宙热寂。所以SHA-256的碰撞安全性,在可预见的未来是牢不可破的。

注意: 碰撞安全性不等于原像安全性。抗碰撞要求找到任意两个不同的输入产生相同输出,而抗原像要求给定哈希值找到原始输入。后者的安全强度是2^n,比碰撞高得多。所以别把「碰撞安全」和「原像安全」搞混了。

4.4 HMAC构造:消息认证码的正确姿势

哈希函数本身不提供认证功能。你收到一段消息和它的哈希值,怎么确认这哈希值没被篡改?答案是加盐——也就是密钥。HMAC(Hash-based Message Authentication Code)就是干这个的。

HMAC的标准定义是:

HMAC(K, m) = H((K' ⊕ opad) || H((K' ⊕ ipad) || m))

其中:

  • K'是经过填充或哈希处理的密钥
  • ipad是0x36重复的字节串
  • opad是0x5C重复的字节串
  • ||表示拼接

为什么要搞这么复杂?直接H(K || m)不行吗?

不行。我曾经在早期的一个项目中就犯过这个错——直接用H(K || m)做消息认证。结果被安全审计发现存在长度扩展攻击漏洞。攻击者可以在不知道密钥的情况下,构造出合法的H(K || m || padding || extra)。嗯,那次被批得很惨。

HMAC通过内外两次哈希,彻底解决了这个问题。它的安全性证明依赖于底层哈希函数的伪随机函数性质,而不是抗碰撞性。所以即使有一天SHA-256的碰撞被攻破,HMAC-SHA-256仍然可能是安全的。

实践建议: 做API签名时,优先使用HMAC-SHA256。密钥长度建议32字节以上,且必须是高质量的随机数。我在给支付网关设计签名方案时,就强制要求HMAC密钥每90天轮换一次。

4.5 SM3国密算法:中国的国家标准

SM3是中国国家密码管理局发布的哈希算法标准,输出256位摘要。它的设计思路跟SHA-256类似,但细节上有不少差异:

  • 消息分组长度也是512位,输出256位
  • 压缩函数有64轮,但每轮的布尔函数和置换跟SHA-256不同
  • 使用了16个32位寄存器的线性反馈移位寄存器(LFSR)来生成每轮的消息字

SM3的安全性经过国内外的广泛分析,目前没有发现实质性漏洞。它的碰撞安全强度也是2^128,跟SHA-256相当。

我在做政务系统等保三级测评时,遇到过必须使用国密算法的场景。SM3配合SM2(椭圆曲线公钥密码)和SM4(分组密码),构成了完整的国密套件。如果你做的是面向政府或金融的系统,这块是绕不过去的。

一句话总结: SM3在安全强度上对标SHA-256,但算法细节不同。国内合规场景必须用SM3,国际通用场景用SHA-256或SHA-3。

4.6 知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到哈希家族的分支、HMAC的构造位置、以及SM3的定位。

哈希与消息认证知识体系 哈希函数家族 SHA-2 家族 SHA-3 家族 SM3 国密 SHA-256 SHA-512 SHA3-256 SHAKE128 HMAC 构造 HMAC-SHA256 HMAC-SM3 核心原则:SHA-256为基线,SHA-3面向未来,SM3满足合规,HMAC提供认证

4.7 实践中的选择建议

说了这么多理论,最后给点实在的。如果你现在要设计一个系统,哈希部分怎么选?

  • 通用场景:SHA-256。成熟、高效、安全,所有平台都支持。
  • 新协议设计:SHA3-256或SHAKE128。避免长度扩展攻击,设计更现代。
  • 国内合规:SM3。等保、密评、政务系统绕不开。
  • 消息认证:HMAC-SHA256。密钥长度32字节,定期轮换。
  • 密码哈希存储:别用普通哈希!用bcrypt、scrypt或Argon2。这个后面章节会细讲。
避坑指南: 我曾经见过一个项目,用SHA-256直接哈希用户密码存数据库。结果被脱库后,攻击者用彩虹表几分钟就还原了大部分弱密码。记住:哈希函数不是为慢速计算设计的,密码哈希必须用专门的慢哈希算法。