哈希函数与消息完整性:SHA-256原理、HMAC、数字签名基础
哈希函数是密码学中最基础也最常用的原语之一。它能够将任意长度的输入数据压缩为固定长度的摘要(Digest),并且具备单向性和抗碰撞性。本章从 SHA-256 的内部结构出发,逐步讲解如何利用哈希函数构造消息认证码(HMAC)以及数字签名,从而保障数据的完整性与来源可信性。
2.1 哈希函数核心属性
一个安全的密码学哈希函数必须满足以下三个安全属性:
| 属性 | 定义 | 违反后果 |
|---|---|---|
| 抗原像性 | 给定摘要 y,无法找到任何 x 使得 H(x) = y | 攻击者可伪造摘要对应的原始消息 |
| 抗第二原像性 | 给定 x₁,无法找到 x₂ ≠ x₁ 使得 H(x₁) = H(x₂) | 攻击者可替换消息而不改变摘要 |
| 抗碰撞性 | 无法找到任意两个不同的 x₁、x₂ 使得 H(x₁) = H(x₂) | 生日攻击可伪造数字签名 |
关键指标:SHA-256 输出 256 位摘要,抗碰撞安全强度为 128 位(生日攻击复杂度 2¹²⁸)。截至 2025 年,SHA-256 仍被 NIST 认定为适合大多数商用场景的安全哈希算法。
2.2 SHA-256 算法原理
SHA-256 属于 SHA-2 家族,采用 Merkle-Damgård 结构。处理流程分为三个阶段:
- 填充与长度编码:在消息末尾追加 1 位 '1',再填充若干 '0',最后附加 64 位原始消息长度(大端序),使总长度恰好为 512 位的整数倍。
- 消息分块:将填充后的消息划分为 512 位(64 字节)的块,每块进一步拆分为 16 个 32 位字。
- 压缩函数迭代:每个块经过 64 轮轮函数处理,使用 8 个初始工作变量(A-H)和 64 个常数 K₀…K₆₃,最终输出 256 位摘要。
实现提示:SHA-256 的 64 轮轮函数中,每轮使用不同的轮常数 Kᵢ(前 64 个质数的立方根小数部分的前 32 位)。这些常数是固定的,无需在运行时计算。
2.3 HMAC:基于哈希的消息认证码
单纯使用哈希函数只能检测消息是否被篡改,但无法验证消息的来源。HMAC(Hash-based Message Authentication Code)通过在哈希计算中混入共享密钥,同时提供完整性和真实性保护。
HMAC-SHA256 的计算公式如下:
HMAC(K, m) = SHA256( (K' ⊕ opad) || SHA256( (K' ⊕ ipad) || m ) )
其中:
K' = 如果 K 长度 > 64 字节,则 K' = SHA256(K);否则 K' = K(左补零至 64 字节)
ipad = 0x3636…36(64 字节)
opad = 0x5C5C…5C(64 字节)
|| = 字节串拼接
常见误区:不要直接使用 H(K || m) 或 H(m || K) 作为消息认证码。长度扩展攻击可针对 Merkle-Damgård 结构构造有效 MAC,而 HMAC 的双层嵌套结构可有效防御此类攻击。
2.4 数字签名基础
数字签名解决了 HMAC 的密钥分发问题——HMAC 要求通信双方预先共享密钥,而数字签名使用非对称密钥对:签名者用私钥签名,验证者用公钥验证。典型的数字签名方案包含三个算法:
- KeyGen(1ⁿ) → (sk, pk):生成私钥 sk 和公钥 pk。
- Sign(sk, m) → σ:使用私钥对消息 m 生成签名 σ。
- Verify(pk, m, σ) → {0,1}:使用公钥验证签名是否有效。
实际应用中,数字签名通常先对消息做哈希,再对摘要签名,而非直接对长消息签名。这样做有两个好处:
- 提升效率:非对称签名运算只处理固定长度的摘要(如 256 位)。
- 兼容性:不同长度的消息都能统一签名流程。
2.5 三种完整性机制对比
| 机制 | 密钥类型 | 提供完整性 | 提供真实性 | 抗抵赖性 | 典型算法 |
|---|---|---|---|---|---|
| 哈希(Hash) | 无密钥 | ✅ | ❌ | ❌ | SHA-256 |
| 消息认证码(MAC) | 对称密钥 | ✅ | ✅ | ❌ | HMAC-SHA256 |
| 数字签名 | 非对称密钥对 | ✅ | ✅ | ✅ | ECDSA / RSA-PSS |
选型建议:在端到端加密设计中,消息完整性通常由 HMAC 或 AEAD(认证加密)提供;数字签名主要用于证书签名、代码签名和区块链交易等需要公开验证的场景。如果通信双方已通过密钥交换协议(如 X3DH)获得共享密钥,优先使用 HMAC 而非数字签名,因为对称运算性能远优于非对称运算。