从一个困惑开始

你可能在不同场景见过这些词: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
2
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello!") = ce06092fb948d9ffac7d1a376e404b26b7575bcc11ee05a4615fef4fec3a308b

Hash 能做什么: 验证数据完整性——你下载了一个文件,对比 SHA-256 值就知道有没有被篡改。

Hash 不能做什么: 证明"谁"发送了数据。任何人都能计算 Hash,没有秘密参与。

详见本博客 散列算法 一文。


Layer 1:MAC / HMAC——对称密钥签名

什么是 MAC

MAC(Message Authentication Code,消息认证码)在 Hash 的基础上引入一个共享密钥。只有持有密钥的人才能生成和验证 MAC 值。

1
MAC = F(key, message)

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
HMAC(K, m) = H((K' ⊕ opad) ‖ H((K' ⊕ ipad) ‖ m))
  • 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 的安全性取决于:

  1. 底层 Hash 函数的强度(推荐 SHA-256 或更强)
  2. 密钥长度(推荐 ≥ Hash 输出长度,即 256 bit)
  3. 密钥的随机性(必须用 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
signature = (Hash(message))^d mod n

验证过程:

1
Hash(message) == signature^e mod n ?

其中 (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 握手中,签名出现在两个关键位置:

  1. 证书验证:验证服务器证书链的签名(X.509)
  2. 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 本身没有安全问题,前提是:

  1. 密钥足够长且随机(≥ 256 bit)
  2. 签发方和验证方是同一组织
  3. 不需要公开验证

微服务跨团队场景才需要 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)

参考与延伸阅读

本博客相关文章:

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)

经典事故: