密码学签名全景:从 HMAC 到数字签名的完整图谱
从一个困惑开始
你可能在不同场景见过这些词:HMAC-SHA256、RSA 签名、ECDSA、HS256、RS256、X.509 证书签名……它们都叫"签名",但到底有什么区别?什么时候该用哪个?
这篇文章一次性理清整个图谱。
签名家族的完整分类
先看全貌——所有"签名"机制可以按照是否使用密钥和密钥类型分为三层:
核心区别一句话:
| 维度 | 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 函数包两层:
用公式表达:
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 的根本区别
不可否认性(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)的签名恰好是这套分类的完美体现:
选型指南:
| 场景 | 推荐算法 | 理由 |
|---|---|---|
| 单体应用、内部服务 | 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 的公钥验证。
证书中的签名算法字段(signatureAlgorithm)常见值:
sha256WithRSAEncryption(目前最普遍)ecdsa-with-SHA256(现代证书趋势)Ed25519(前沿,浏览器支持逐步完善)
关于 X.509 证书结构详见本博客 X.509证书问题 一文。
关于 TLS 握手中的证书验证详见 TLS 握手与加密通信 一文。
API 请求签名
这是工程中最常遇到的签名场景。几乎所有支付和云服务 API 都使用 HMAC 签名:
AWS Signature V4 流程(简化):
支付宝/微信支付的签名:
| 平台 | 早期方案 | 现行方案 |
|---|---|---|
| 支付宝 | 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 签名
签名算法演进时间线
决策树:如何选择签名方案
常见误区与澄清
误区 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 首次实际碰撞
