密码学签名全景:从 HMAC 到数字签名的完整图谱
从一个困惑开始
你可能在不同场景见过这些词:HMAC-SHA256、RSA 签名、ECDSA、HS256、RS256、X.509 证书签名……它们都叫"签名",但到底有什么区别?什么时候该用哪个?
这篇文章一次性理清整个图谱。
签名家族的完整分类
先看全貌——所有"签名"机制可以按照是否使用密钥和密钥类型分为三层:
graph TD
subgraph "Layer 0: 无密钥"
H["Hash 函数<br/>SHA-256 / SHA-3"]
end
subgraph "Layer 1: 对称密钥"
MAC["MAC 消息认证码"]
HMAC["HMAC<br/>HMAC-SHA256"]
CMAC["CMAC<br/>AES-CMAC"]
MAC --> HMAC
MAC --> CMAC
end
subgraph "Layer 2: 非对称密钥"
DS["Digital Signature 数字签名"]
RSA["RSA 签名<br/>PKCS#1 v1.5 / PSS"]
ECDSA["ECDSA<br/>secp256k1 / P-256"]
EdDSA["EdDSA<br/>Ed25519 / Ed448"]
DS --> RSA
DS --> ECDSA
DS --> EdDSA
end
H -->|"加入对称密钥"| MAC
H -->|"加入非对称密钥"| DS
style H fill:#fef3c7,stroke:#d97706
style MAC fill:#dbeafe,stroke:#2563eb
style DS fill:#ede9fe,stroke:#7c3aed
核心区别一句话:
| 维度 | Hash | MAC (HMAC) | Digital Signature |
|---|---|---|---|
| 密钥 | 无 | 对称(共享密钥) | 非对称(私钥签/公钥验) |
| 证明了什么 | 数据未被篡改 | 数据未被篡改 + 来自持有密钥的人 | 数据未被篡改 + 来自私钥持有者 |
| 不可否认性 | 无 | 无(双方都有密钥,无法区分谁生成的) | 有(只有私钥持有者能生成) |
| 性能 | 最快 | 快 | 慢(10-100x) |
| 典型用途 | 文件校验、去重 | API 签名、Cookie 防篡改 | 证书、代码签名、法律效力 |
Layer 0:Hash——没有秘密的指纹
Hash 函数是一切的基础。它把任意长度的输入压缩成固定长度的"指纹"(digest),但不涉及任何密钥。
1 | |
Hash 能做什么: 验证数据完整性——你下载了一个文件,对比 SHA-256 值就知道有没有被篡改。
Hash 不能做什么: 证明"谁"发送了数据。任何人都能计算 Hash,没有秘密参与。
详见本博客 散列算法 一文。
Layer 1:MAC / HMAC——对称密钥签名
什么是 MAC
MAC(Message Authentication Code,消息认证码)在 Hash 的基础上引入一个共享密钥。只有持有密钥的人才能生成和验证 MAC 值。
1 | |
HMAC 的内部结构
HMAC(Hash-based MAC)是最常用的 MAC 构造,由 RFC 2104 定义。它的核心思想是用密钥把 Hash 函数包两层:
graph LR
subgraph "HMAC-SHA256(key, message)"
K["Key"] --> PAD1["key ⊕ ipad"]
PAD1 --> HASH1["SHA-256(ipad‖message)"]
HASH1 --> CONCAT["opad ‖ inner_hash"]
K --> PAD2["key ⊕ opad"]
PAD2 --> CONCAT
CONCAT --> HASH2["SHA-256(opad‖inner_hash)"]
HASH2 --> OUT["HMAC 输出<br/>32 bytes"]
end
style OUT fill:#dbeafe,stroke:#2563eb
用公式表达:
1 | |
ipad= 0x36 重复到块大小opad= 0x5c 重复到块大小K'= 如果 key 过长则先 Hash,过短则零填充
为什么不能直接 H(key ‖ message)?
这是新手最常见的错误设计。直接拼接存在长度扩展攻击(Length Extension Attack):
对于 Merkle-Damgård 结构的 Hash(MD5、SHA-1、SHA-256),攻击者在不知道 key 的情况下,只要知道 H(key ‖ message) 的值和 message 的长度,就能计算出 H(key ‖ message ‖ padding ‖ extra) 的值。
HMAC 的双层结构正是为了抵抗这类攻击。
HMAC 的安全性保证
HMAC 的安全性取决于:
- 底层 Hash 函数的强度(推荐 SHA-256 或更强)
- 密钥长度(推荐 ≥ Hash 输出长度,即 256 bit)
- 密钥的随机性(必须用 CSPRNG 生成)
HMAC 的典型应用
| 场景 | 具体实例 |
|---|---|
| JWT 对称签名 | HS256 = HMAC-SHA256 |
| API 请求签名 | AWS Signature V4、支付宝/微信支付签名 |
| Cookie 防篡改 | Rails MessageVerifier、Spring Session |
| TLS 记录层 | MAC 保护每条 TLS record |
| TOTP/HOTP | Google Authenticator 动态口令 |
| Key Derivation | HKDF(基于 HMAC 的密钥派生) |
Layer 2:数字签名——非对称密钥签名
数字签名与 HMAC 的根本区别
sequenceDiagram
participant A as Alice (签名者)
participant B as Bob (验证者)
participant E as Eve (第三方)
Note over A,B: === HMAC 场景 ===
A->>B: message + HMAC(shared_key, message)
Note over B: Bob 能验证(他也有 shared_key)
Note over E: Eve 无法验证(没有 shared_key)
Note over B: ⚠️ Bob 也能伪造签名!<br/>因为他也有 shared_key
Note over A,B: === 数字签名场景 ===
A->>B: message + Sign(private_key, message)
Note over B: Bob 用 Alice 的 public_key 验证 ✓
Note over E: Eve 也能用 public_key 验证 ✓
Note over B: ✅ Bob 无法伪造签名<br/>因为他没有 private_key
不可否认性(Non-repudiation) 是数字签名独有的特性:
- HMAC:双方都有密钥 → 无法证明"是谁生成的"
- 数字签名:只有私钥持有者能签 → 签了就不能抵赖
RSA 签名
RSA 是最经典的数字签名算法,基于大整数分解的数学困难问题。
签名过程:
1 | |
验证过程:
1 | |
其中 (e, n) 是公钥,(d, n) 是私钥。
RSA 签名的两种 padding 方案:
| 方案 | 标准 | 安全性 | 推荐 |
|---|---|---|---|
| PKCS#1 v1.5 | RFC 8017 | 存在 Bleichenbacher 攻击变体 | 遗留系统兼容 |
| RSA-PSS | RFC 8017 | 有安全性证明 | 新系统首选 |
RSA 密钥长度与安全性:
| 密钥长度 | 安全等级 | 状态 |
|---|---|---|
| 1024 bit | ~80 bit | 已不安全,NIST 2013 年起禁用 |
| 2048 bit | ~112 bit | 当前最低要求 |
| 3072 bit | ~128 bit | 推荐 |
| 4096 bit | ~140 bit | 高安全场景 |
ECDSA(椭圆曲线数字签名)
ECDSA 基于椭圆曲线离散对数问题,用更短的密钥达到同等安全性。
与 RSA 的对比:
| 安全等级 | RSA 密钥 | ECDSA 密钥 | 签名大小 |
|---|---|---|---|
| 128 bit | 3072 bit | 256 bit | RSA: 384B / ECDSA: 64B |
| 192 bit | 7680 bit | 384 bit | RSA: 960B / ECDSA: 96B |
| 256 bit | 15360 bit | 521 bit | RSA: 1920B / ECDSA: 132B |
常见曲线:
| 曲线 | 标准 | 用途 |
|---|---|---|
| P-256 (secp256r1) | NIST | TLS、JWT(ES256) |
| P-384 (secp384r1) | NIST | 高安全 TLS(ES384) |
| secp256k1 | SEC | Bitcoin、Ethereum |
ECDSA 的一个致命陷阱: 签名时使用的随机数 k 如果重复或可预测,私钥会直接泄露。PlayStation 3 的密钥泄露事件(2010 年)正是因为 Sony 在 ECDSA 签名中使用了固定的 k 值。
EdDSA(Edwards-curve Digital Signature Algorithm)
EdDSA 是最新一代签名算法,设计目标是消除 ECDSA 的工程陷阱:
| 特性 | ECDSA | EdDSA |
|---|---|---|
| 随机数依赖 | 需要高质量随机数 | 确定性(从私钥+消息派生) |
| 实现复杂度 | 高(侧信道防护难) | 低(常量时间实现) |
| 签名速度 | 中 | 快(比 ECDSA 快 2-3x) |
| 验证速度 | 中 | 快(支持批量验证) |
| 标准 | FIPS 186-4 | RFC 8032 |
EdDSA 的两个实例:
- Ed25519:基于 Curve25519,128 bit 安全,签名 64 字节,公钥 32 字节
- Ed448:基于 Curve448,224 bit 安全
Ed25519 已成为现代系统的首选:SSH(OpenSSH 默认)、Signal 协议、WireGuard VPN、TLS 1.3 均支持。
与 Schnorr 签名的关系: EdDSA 的设计直接继承了 Schnorr 签名的核心思想(基于离散对数的三步协议:commitment → challenge → response)。Schnorr 签名本身因专利限制长期未被广泛标准化,直到 2008 年专利过期后才在 Bitcoin Taproot(BIP 340,2021 年激活)中大规模部署。BIP 340 的 Schnorr 变体基于 secp256k1 曲线,而 EdDSA 选择了 Edwards 曲线实现相同思想。两者共享"确定性 nonce + 线性签名方程"的设计优势。
应用场景全对照
JWT 签名算法
JWT(JSON Web Token)的签名恰好是这套分类的完美体现:
graph TD
JWT["JWT 签名算法"]
JWT --> SYM["对称类(HMAC)"]
JWT --> ASYM["非对称类(Digital Signature)"]
SYM --> HS256["HS256<br/>HMAC-SHA256"]
SYM --> HS384["HS384<br/>HMAC-SHA384"]
SYM --> HS512["HS512<br/>HMAC-SHA512"]
ASYM --> RS256["RS256<br/>RSASSA-PKCS1-v1_5 + SHA-256"]
ASYM --> RS384["RS384"]
ASYM --> PS256["PS256<br/>RSASSA-PSS + SHA-256"]
ASYM --> ES256["ES256<br/>ECDSA P-256 + SHA-256"]
ASYM --> EdDSA_JWT["EdDSA<br/>Ed25519"]
style SYM fill:#dbeafe,stroke:#2563eb
style ASYM fill:#ede9fe,stroke:#7c3aed
选型指南:
| 场景 | 推荐算法 | 理由 |
|---|---|---|
| 单体应用、内部服务 | HS256 | 签发方=验证方,性能最好 |
| 微服务架构 | RS256 或 ES256 | 认证中心签发,各服务只需公钥验证 |
| 高安全 / 合规要求 | ES256 或 EdDSA | 密钥短、签名小、安全性高 |
| 移动端/IoT | ES256 | 计算资源有限,签名体积小 |
JWT HS256 的一个容易犯的错误: 用一个弱密码(如 “secret”)作为 HMAC key。攻击者可以暴力破解 HMAC key 后伪造任意 token。HMAC key 必须是 256 bit 以上的随机值。
X.509 证书签名
X.509 证书是数字签名最经典的应用——CA 用自己的私钥对证书内容签名,浏览器用 CA 的公钥验证。
graph TD
ROOT["Root CA<br/>自签名证书<br/>内置于 OS/浏览器"]
INTER["Intermediate CA<br/>由 Root CA 签名"]
LEAF["服务器证书<br/>由 Intermediate CA 签名"]
ROOT -->|"Root 私钥签名"| INTER
INTER -->|"Intermediate 私钥签名"| LEAF
CLIENT["浏览器/客户端"]
CLIENT -->|"1. 用 Intermediate 公钥验证 Leaf"| LEAF
CLIENT -->|"2. 用 Root 公钥验证 Intermediate"| INTER
CLIENT -->|"3. Root 在信任锚列表中 ✓"| ROOT
style ROOT fill:#fef3c7,stroke:#d97706
style INTER fill:#dbeafe,stroke:#2563eb
style LEAF fill:#dcfce7,stroke:#16a34a
证书中的签名算法字段(signatureAlgorithm)常见值:
sha256WithRSAEncryption(目前最普遍)ecdsa-with-SHA256(现代证书趋势)Ed25519(前沿,浏览器支持逐步完善)
关于 X.509 证书结构详见本博客 X.509证书问题 一文。
关于 TLS 握手中的证书验证详见 TLS 握手与加密通信 一文。
API 请求签名
这是工程中最常遇到的签名场景。几乎所有支付和云服务 API 都使用 HMAC 签名:
AWS Signature V4 流程(简化):
graph LR
REQ["HTTP 请求"] --> CR["① Canonical Request<br/>规范化请求字符串"]
CR --> STS["② String to Sign<br/>= 算法 + 时间 + 范围 + Hash(CR)"]
STS --> SK["③ 派生签名密钥<br/>HMAC(HMAC(HMAC(HMAC(<br/>'AWS4'+secret, date),<br/>region), service), 'aws4_request')"]
SK --> SIG["④ 签名<br/>HMAC-SHA256(signing_key, string_to_sign)"]
SIG --> HEAD["⑤ Authorization Header"]
style SIG fill:#dbeafe,stroke:#2563eb
支付宝/微信支付的签名:
| 平台 | 早期方案 | 现行方案 |
|---|---|---|
| 支付宝 | MD5(params + key)(已废弃) | RSA2(SHA256WithRSA, PKCS#1 v1.5) |
| 微信支付 | HMAC-SHA256 (v2) | RSA-SHA256 with PKCS#1 v1.5 (v3) |
注意演进方向:从 HMAC(对称)→ RSA/ECDSA(非对称)。原因是非对称签名支持公钥验签——商户可以验证回调确实来自支付平台,而无需共享密钥。
TLS 中的签名
TLS 1.3 握手中,签名出现在两个关键位置:
- 证书验证:验证服务器证书链的签名(X.509)
- CertificateVerify 消息:服务器用自己的私钥对握手 transcript 签名,证明"我确实拥有证书中的公钥对应的私钥"
TLS 1.3 支持的签名算法(signature_algorithms 扩展):
rsa_pss_rsae_sha256(RSA-PSS)ecdsa_secp256r1_sha256(ECDSA P-256)ed25519(EdDSA)
TLS 1.3 已移除:
- RSA PKCS#1 v1.5 签名(仅保留用于证书)
- SHA-1 签名
签名算法演进时间线
timeline
title 签名算法演进
1977 : RSA 发明
1991 : DSA (NIST FIPS 186)
1996 : HMAC (RFC 2104)
1999 : ECDSA (ANSI X9.62)
2005 : SHA-1 理论攻破
2011 : Ed25519 发布 (Bernstein)
2013 : NIST 禁用 1024-bit RSA
2017 : SHA-1 实际碰撞 (SHAttered)
2018 : TLS 1.3 (RFC 8446)<br/>移除 RSA PKCS#1 v1.5 签名
2023 : FIPS 186-5 正式发布,EdDSA 纳入 DSS 标准
2023 : 后量子签名标准<br/>CRYSTALS-Dilithium (FIPS 204)
决策树:如何选择签名方案
graph TD
START["需要签名/验证"] --> Q1{"签名方和验证方<br/>是同一方/互信方?"}
Q1 -->|"是"| Q2{"需要不可否认性?"}
Q1 -->|"否(公开验证)"| ASYM["使用数字签名"]
Q2 -->|"不需要"| HMAC_CHOICE["使用 HMAC"]
Q2 -->|"需要"| ASYM
HMAC_CHOICE --> HMAC_ALG["HMAC-SHA256<br/>key ≥ 256 bit 随机"]
ASYM --> Q3{"优先考虑什么?"}
Q3 -->|"兼容性"| RSA_CHOICE["RSA-PSS<br/>≥ 2048 bit"]
Q3 -->|"性能 + 密钥大小"| EC_CHOICE{"需要 FIPS 合规?"}
EC_CHOICE -->|"是"| ECDSA_CHOICE["ECDSA P-256"]
EC_CHOICE -->|"否"| ED_CHOICE["Ed25519<br/>(首选)"]
style HMAC_ALG fill:#dbeafe,stroke:#2563eb
style ED_CHOICE fill:#dcfce7,stroke:#16a34a
style ECDSA_CHOICE fill:#dcfce7,stroke:#16a34a
style RSA_CHOICE fill:#fef3c7,stroke:#d97706
常见误区与澄清
误区 1:"加密"和"签名"是一回事
完全不同的操作:
- 加密:用公钥加密,私钥解密 → 保密性
- 签名:用私钥签名,公钥验证 → 真实性 + 完整性
RSA 碰巧两个方向都能用,但这是 RSA 的特殊性质,不是普遍规律。ECDSA/EdDSA 只能签名,不能加密。
误区 2:“HMAC 不安全,数字签名才安全”
安全性取决于威胁模型:
- 如果通信双方互信(如你的前端和你的后端),HMAC 完全安全且性能更好
- 如果需要向第三方证明"这确实是 Alice 签的",才需要数字签名
误区 3:“SHA-256 签名”
不存在"SHA-256 签名"这个东西。SHA-256 是 Hash 函数,不是签名算法。准确的说法是:
- HMAC-SHA256(用 SHA-256 构造 HMAC)
- RSA with SHA-256(先 Hash 再用 RSA 签名)
- ECDSA with SHA-256(先 Hash 再用 ECDSA 签名)
误区 4:“X.509 是一种签名算法”
X.509 是证书格式标准(ITU-T 定义),不是签名算法。X.509 证书内部使用 RSA/ECDSA/EdDSA 等算法进行签名。"X.几几几"系列(X.500、X.509、X.520 等)是 ITU-T 的目录服务标准族,X.509 专门定义公钥证书的格式。
误区 5:“JWT 用了 HMAC 就不安全”
JWT + HS256 本身没有安全问题,前提是:
- 密钥足够长且随机(≥ 256 bit)
- 签发方和验证方是同一组织
- 不需要公开验证
微服务跨团队场景才需要 RS256/ES256。
后量子时代:下一代签名
量子计算机威胁所有基于整数分解(RSA)和离散对数(ECDSA/EdDSA)的签名算法。NIST 于 2024 年正式发布后量子签名标准:
| 算法 | 标准 | 签名大小 | 公钥大小 | 安全假设 |
|---|---|---|---|---|
| ML-DSA (Dilithium) | FIPS 204 | ~2.4 KB | ~1.3 KB | 格(Lattice)问题 |
| SLH-DSA (SPHINCS+) | FIPS 205 | ~7-49 KB | 32-64 B | Hash 函数安全 |
签名体积显著增大,但这是抵抗量子攻击的代价。当前建议:新系统可以考虑"混合模式"——同时使用传统算法和后量子算法。
速查表
| 我要… | 用这个 | 具体选择 |
|---|---|---|
| 验证文件未被篡改 | Hash | SHA-256 |
| 验证 API 请求真实性(双方共享密钥) | HMAC | HMAC-SHA256 |
| JWT 签名(单体应用) | HMAC | HS256 |
| JWT 签名(微服务) | 数字签名 | ES256 或 EdDSA |
| TLS 证书 | 数字签名 | ECDSA P-256 或 RSA-2048 |
| SSH 密钥 | 数字签名 | Ed25519 |
| 代码签名 | 数字签名 | RSA-4096 或 ECDSA |
| 区块链交易 | 数字签名 | ECDSA secp256k1 (BTC/ETH) |
| 未来抗量子 | 后量子签名 | ML-DSA (Dilithium) |
参考与延伸阅读
本博客相关文章:
- 散列算法 — Hash 函数家族详解
- X.509证书问题 — 证书结构与签发流程
- TLS 握手与加密通信 — TLS 协议中签名的实际运用
- Web 会话与身份认证全景 — JWT 签名在认证体系中的位置
RFC 与标准:
- RFC 2104 — HMAC: Keyed-Hashing for Message Authentication
- RFC 8017 — PKCS #1: RSA Cryptography Specifications (v2.2)
- RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA)
- RFC 7518 — JSON Web Algorithms (JWA)
- FIPS 186-5 — Digital Signature Standard (DSS)
- FIPS 204 — Module-Lattice-Based Digital Signature (ML-DSA)
经典事故:
- PlayStation 3 ECDSA 密钥泄露 (2010) — 重复随机数导致私钥可恢复
- Debian OpenSSL 弱随机数 (2008) — 所有在受影响系统生成的密钥均不安全
- SHAttered (2017) — SHA-1 首次实际碰撞
