哈希函数与消息认证码: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:已废弃。别用。真的别用。
注意:SHA-2 和 SHA-3 不是“升级版”关系,它们是两种不同设计的哈希函数。SHA-3 没有抛弃 SHA-2,而是作为备胎。万一 SHA-2 哪天被打穿,SHA-3 还能顶上。

3. HMAC:给哈希加把钥匙

纯粹哈希无法防止篡改——攻击者可以同时改消息和哈希值。HMAC 用共享密钥解决了这个问题:HMAC(K, m) = H( (K' ⊕ opad) || H( (K' ⊕ ipad) || m ) )。

公式看着复杂,但你可以理解为“用密钥把哈希包了两层”。我曾经在物联网设备里实现过一个最小化 HMAC,当时密钥直接从熵池取,结果发现密钥太短导致碰撞概率上升。后来改成了至少 32 字节密钥,才消停。

HMAC 的应用场景:

  1. API 请求签名(比如 AWS Signature V4)
  2. 消息完整性校验(TLS 的 MAC 层)
  3. 密钥派生(HKDF 就是基于 HMAC)
经验:密钥别硬编码在代码里。我见过不少把 HMAC 密钥放配置文件的情况——那跟没有钥匙差不多。用密钥管理服务或至少环境变量注入。

4. 数字签名概述:从对称到非对称

HMAC 要求双方共享同一个密钥。如果你要给全球的用户验证消息来源,共享密钥就不可行——你没法安全地把密钥发给每个人。数字签名用公钥加密体系解决了这个问题。

数字签名 vs HMAC:

对比维度HMAC数字签名
密钥管理对称(共享密钥)非对称(私钥签名,公钥验签)
不可否认性❌ 双方都能伪造✅ 只有持私钥者能签名
性能快(对称加密/哈希)慢(公钥运算)
典型算法HMAC-SHA256RSA-PSS, ECDSA, EdDSA

实际流程很简单:签名方 对消息哈希后,用私钥加密哈希得到签名;验签方 用公钥解密签名得到哈希,再与本地计算的消息哈希做比较。你想想看,如果两个哈希一致,那说明消息没被改,且签名确实来自私钥持有者——这就是不可否认性。

核心逻辑:数字签名 = 哈希(压缩消息) + 非对称加密(保护摘要)。没有哈希,直接对长消息签名会非常慢;没有公钥加密,哈希本身不防伪造。

5. 三者关系图谱

我用一张 SVG 把哈希、HMAC、数字签名的串联关系画出来。你一眼就能看懂它们怎么配合。

哈希 · HMAC · 数字签名 关系图 原始消息 m SHA-256 消息 共享密钥 K HMAC H(K,m) → mac_tag 数字签名 Sign(私钥, hash) → sig 私钥 sk 📌 哈希压缩消息 → HMAC 用共享密钥提供完整性 → 数字签名用私钥提供不可否认性。 三者层层递进,构成可信通信的基石。

6. 避坑指南

这些年看我踩过的坑,你要是能避开,至少省两晚上调试时间:

  • 哈希不等于加密。哈希不可逆,加密可逆。有人把 SHA256 当加密用,结果“解密”时懵了——这是原则性错误。
  • HMAC 的密钥长度不能小于哈希输出长度。我用 HMAC-SHA256 时最少给 32 字节。短了会降低安全性。
  • 数字签名的哈希算法要跟签名算法匹配。比如 RSA 签名建议用 SHA-256 而不是 SHA-1,否则签名虽然能用,但攻击者可能碰撞哈希绕过验签。
  • 不要自己拼签名方案。我曾经试着“手动”组合 RSA + SHA256,结果忘了加 padding,导致存在特定攻击。请直接用标准接口,比如 RSA-PSS 或 ECDSA。
小技巧:测试阶段可以故意篡改消息或签名,验证程序能否正确检测到不匹配。我曾经在 CI 里加了这个测试,捕获过好几次集成时的接口变更问题。

好了,哈希、HMAC、数字签名这三个概念你心里应该有数了。它们之间的关系就像:哈希是“指纹仪”,HMAC 是“验指纹的保安”,数字签名则是“公证处的公章”——各有各的活,但都离不开那个指纹。