🏠 总目录📚 本教程 17 · 数字签名
📑 本页目录(点开跳转)

17 · 数字签名

30 分钟 | ⭐ 唯一能提供"不可否认"的工具


🎯 一句话

MAC 能证明"消息没被改",但验证者自己也能伪造。 数字签名用私钥签、公钥验 —— 只有一个人能签,全世界都能验,而且他事后赖不掉。


🖋️ 一、定义与安全目标

   Gen()      → (pk, sk)
   Sign(sk, m) → σ
   Verify(pk, m, σ) → 0/1

安全定义:UF-CMA(选择消息攻击下的存在性不可伪造)

   攻击者拿到 pk,可以让你签【任意多条】他挑的消息,
   仍然无法为【一条没签过的消息】造出合法签名 ⭐

⚠️ 一个更强的要求:SUF-CMA(强不可伪造) 连"为已签过的消息造出另一个不同的合法签名"都不行。 💡 为什么重要:如果签名可延展(malleable), 区块链上就能改变交易 ID —— 这就是比特币著名的 交易延展性漏洞, Mt. Gox 曾以此为破产理由之一。

MAC 数字签名
密钥 对称(双方共享) 非对称
谁能验证 只有持密钥的人 任何人
不可否认 ❌(验证者也能伪造)
速度 快(微秒) 慢(毫秒)
签名长度 16–32 字节 64–384 字节

🔑 二、RSA 签名

❌ 教科书版本

   σ = M^d mod n,验证 σ^e ≟ M

   💥 三个致命问题:
   ① 存在性伪造:随便选一个 σ,算 M = σ^e
      → (M, σ) 就是一个合法的"签名对"!攻击者无需私钥 ⭐
   ② 同态伪造:σ₁·σ₂ 是 M₁·M₂ 的合法签名
   ③ 只能签比 n 短的消息

✅ 必须先哈希再填充

   FDH(全域哈希):σ = H(M)^d mod n
   ├─ 哈希破坏了同态性(H(M₁·M₂) ≠ H(M₁)·H(M₂))✅
   ├─ 反算的 σ^e 几乎不可能恰好是某个消息的哈希 ✅
   └─ 任意长度消息都能签 ✅

   PSS(概率签名方案)⭐ 实际标准:
   ├─ 加入随机盐 → 同一消息每次签名不同
   └─ 有更紧的安全归约

💥 PKCS#1 v1.5 签名的 Bleichenbacher'06 攻击: 如果验证实现没有严格检查填充结构(只是"找到 hash 就通过"), e=3 时攻击者可以伪造签名 —— 影响过 OpenSSL、Firefox、多个 JOSE/JWT 库。


📐 三、DSA / ECDSA 与那个致命的 k

   签名(ECDSA):
   ① 选随机 k
   ② r = (kG).x mod n
   ③ s = k⁻¹(H(m) + r·d) mod n      ← d 是私钥
   签名 = (r, s)

💀 k 的三条铁律

   ⭐ ① k 重复 ⟹ 私钥直接泄露

   两个签名用了同一个 k:
     s₁ = k⁻¹(H₁ + rd)
     s₂ = k⁻¹(H₂ + rd)
   ⟹ s₁ - s₂ = k⁻¹(H₁ - H₂)
   ⟹ k = (H₁ - H₂)/(s₁ - s₂)        ← 解出 k
   ⟹ d = (s₁k - H₁)/r                ← 解出私钥 💀

   ⭐ ② k 部分可预测 ⟹ 用格约减(LLL)也能恢复私钥
        (只要泄露每个 k 的几个 bit,几十个签名就够)

   ⭐ ③ k 泄露 ⟹ 单个签名就能算出私钥

💥 真实事故: - PlayStation 3(2010):Sony 把 k 写成固定常数 → 私钥被算出 → 任何人都能签发"官方"固件,主机被彻底越狱 ⭐ - Android 比特币钱包(2013)SecureRandom 缺陷导致 k 重复 → 私钥被算出,钱被盗 - 多个硬件钱包 / IoT 设备:熵不足导致 k 可预测

✅ 解法:确定性签名

   RFC 6979:k = HMAC(私钥, H(消息))
   → 【完全不需要随机数】,同一消息总是产生同一签名 ⭐
   → 熵源坏掉也不会泄露私钥

   Ed25519 内建这个设计 ⟹ 这也是推荐它的重要原因

🔑 这是一个漂亮的工程教训把"需要好随机数"这个脆弱的依赖,替换成"需要一个好哈希" —— 后者可靠得多。


🎩 四、Schnorr 签名与 Fiat–Shamir

Schnorr 签名结构极其简洁

   公钥 y = g^x

   签名:
   ① 选随机 r,算承诺 t = g^r
   ② 挑战 c = H(m ‖ t)          ⭐ 用哈希代替"验证者发挑战"
   ③ 响应 s = r + c·x
   签名 = (t, s)

   验证:g^s ≟ t · y^c
📐 验证为什么成立(想看再点)

$$g^s = g^{r + cx} = g^r \cdot g^{cx} = t \cdot (g^x)^c = t\cdot y^c \quad\checkmark$$

⭐ Fiat–Shamir 转换:一个通用的魔法

   原本:一个【交互式】身份认证协议
        (证明者发承诺 → 验证者发随机挑战 → 证明者响应)

   转换:把"验证者发的随机挑战"替换成 c = H(消息 ‖ 承诺)
        → 变成【非交互式】的签名 ⭐

   💡 为什么安全:哈希的输出不可预测,
      证明者无法"先知道挑战再准备承诺"

🔑 这个转换是密码学里最有用的工具之一任何交互式的零知识证明,都能用它变成一个签名或非交互式证明。 🔗 第 21 章会看到它是 zk-SNARK 的基础。

⚠️ Fiat–Shamir 的经典实现坑哈希必须把承诺 t 和消息 m 都包含进去。 只哈希消息(漏掉 t)会导致可伪造 —— 这被称为 "weak Fiat–Shamir"2023 年还在多个 zk 库里被发现

Schnorr 的优势

优势 说明
可证明安全 在 ROM 下从离散对数归约,比 ECDSA 干净
线性结构 ⭐ 支持多签聚合(MuSig)、门限签名
签名短 64 字节

💡 比特币 2021 年的 Taproot 升级引入了 Schnorr 签名, 主要动机就是多签可以聚合成一个签名 —— 省空间,还提高隐私。


🔍 五、实践中的选型

算法 签名长度 特点
Ed25519 64 B 首选。快、确定性、无 k 值风险
ECDSA P-256 64 B 兼容性好,但 k 值风险高
RSA-PSS 3072 384 B 遗留系统兼容
BLS 48 B 可聚合:一千个签名合成一个(第 16 章配对)
ML-DSA ~2.4 KB 抗量子(第 23 章
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

sk = Ed25519PrivateKey.generate()
sig = sk.sign(b"message")                    # ⭐ 64 字节,确定性
sk.public_key().verify(sig, b"message")      # 失败抛 InvalidSignature

⚠️ 六、签名之外的三个协议层问题

   签名验证通过 ≠ 这条消息可以被接受

   ① 重放:老签名仍然合法 → 需要序号/时间戳(第 11 章)
   ② 上下文混淆:A 场景的签名被拿到 B 场景用
      ✅ 解法:【域分隔】—— 签名前加前缀 "myapp:v1:transfer:" ⭐
   ③ 密钥复用:同一密钥既签名又加密 → 可能互相打穿
      ✅ 解法:不同用途用不同密钥

💥 上下文混淆的真实例子:某些钱包用同一个密钥签"登录挑战"和"转账交易", 攻击者把一个伪装成登录请求的转账交易发给用户签名 → 钱直接没了。 域分隔能完全堵住这类攻击。


🔗 和站内其他章的关系

相关的地方 和这一章的关系
第 11 章 MAC 签名是它的非对称版,多了不可否认
第 14 章 RSA 同态性 教科书签名可伪造的原因
第 9 章 抗碰撞 碰撞 ⟹ 能把签名"搬到"另一条消息上 ⭐
第 4 章 随机数 k 值事故的根因
第 21 章 ZKP Fiat–Shamir 是共用的桥梁
第 18 章 PKI 证书就是 CA 的签名
模型上线之后 · 合规审计与模型卡 那边列的「审计要能回答的五个问题」(决策怎么做的 / 用了哪些数据 / 有没有对某群体不利 / 怎么发现和纠正 / 谁负责)全都默认记录本身是可信的——而「记录有没有被事后改过」它没有回答,数据库时间戳也答不了,只有签名 + 哈希链答得了 ⭐
Claude 资料库 · AI 原生 SDLC 安全 供应链信任的地基:签名的镜像、签名的权重、签名的依赖;⚠️ 但签名只保证「这是我发的」,不保证「这是好的」

✅ 检查点

  1. 数字签名和 MAC 的三个关键区别?
  2. UF-CMA 和 SUF-CMA 的区别?后者为什么重要?
  3. 教科书 RSA 签名的三个问题?
  4. FDH 怎么解决其中两个?
  5. ECDSA 中 k 重复会怎样?写出推导。
  6. RFC 6979 的做法是什么?它体现了什么工程思想?
  7. Fiat–Shamir 转换做了什么?它的经典实现坑是什么?
  8. Schnorr 签名相比 ECDSA 的三个优势?比特币为什么引入它?
  9. 什么是域分隔?它防的是什么攻击?
👀 答案
  1. ①MAC 对称、签名非对称 ②MAC 只有持密钥者能验,签名任何人都能验 ③MAC 不提供不可否认(验证者也能伪造),签名提供。
  2. UF-CMA:无法为没签过的消息造合法签名。SUF-CMA:连"为已签过的消息造出另一个不同的合法签名"都不行。重要是因为签名可延展会改变区块链上的交易 ID——比特币的交易延展性漏洞。
  3. 存在性伪造:随便选 σ 算 M=σ^e 就得到合法签名对 ②同态伪造:σ₁·σ₂ 是 M₁·M₂ 的签名 ③只能签比 n 短的消息。
  4. σ = H(M)^d:哈希破坏同态性(H(M₁M₂)≠H(M₁)H(M₂)),且反算的 σ^e 几乎不可能恰好是某消息的哈希;顺带解决了长度限制。
  5. s₁ = k⁻¹(H₁+rd),s₂ = k⁻¹(H₂+rd) ⟹ s₁−s₂ = k⁻¹(H₁−H₂) ⟹ k = (H₁−H₂)/(s₁−s₂)d = (s₁k−H₁)/r,私钥直接泄露。
  6. k = HMAC(私钥, H(消息))——完全不需要随机数,同一消息总产生同一签名。思想:把"需要好随机数"这个脆弱依赖,替换成"需要一个好哈希"
  7. 把交互式协议里验证者发的随机挑战替换成 c = H(消息‖承诺),变成非交互式签名。坑:哈希必须同时包含承诺 t 和消息 m,漏掉 t 就是 "weak Fiat–Shamir",可伪造——2023 年仍在多个 zk 库中被发现。
  8. 可证明安全(ROM 下从 DLP 干净归约)②线性结构支持多签聚合和门限签名 ③签名短。比特币 Taproot 引入它主要为了多签聚合成一个签名——省空间且提高隐私。
  9. 签名前加上用途前缀(如 "myapp:v1:transfer:")。防上下文混淆——A 场景的签名被拿到 B 场景用。真实例子:钱包用同一密钥签"登录挑战"和"转账交易",攻击者把转账伪装成登录请求让用户签。

🛑 可以停在这里

走神救援

签名 = 私钥签、公钥验,唯一提供不可否认的工具(MAC 的验证者也持密钥所以能伪造)。UF-CMA:签过任意多条也造不出新消息的签名;SUF-CMA 更强(连给已签消息造另一个签名都不行)——重要是因为签名可延展会改变区块链交易 ID(比特币交易延展性漏洞)。教科书 RSA 签名三宗罪:⭐存在性伪造(随便选 σ 算 M=σ^e 就是合法签名对,不需私钥)、同态伪造 σ₁σ₂、长度受限 → ✅必须先哈希(FDH 破坏同态且反算的 σ^e 几乎不可能是某消息的哈希),实际标准是 PSS。💀⭐ECDSA 的 k 三条铁律k 重复 ⟹ k=(H₁−H₂)/(s₁−s₂) ⟹ d=(s₁k−H₁)/r,私钥直接泄露;k 部分可预测 ⟹ 格约减(LLL)几十个签名就能恢复;k 泄露 ⟹ 单签名即破。💥PS3 把 k 写成常数被彻底越狱、Android 比特币钱包 k 重复被盗。✅RFC 6979 确定性签名:k=HMAC(私钥, H(消息)),完全不需要随机数(Ed25519 内建)——⭐把"需要好随机数"这个脆弱依赖换成"需要好哈希"Schnorr:t=g^r,c=H(m‖t),s=r+cx,验证 g^s ≟ t·y^c;⭐Fiat–Shamir 转换=把验证者的随机挑战换成哈希,任何交互式零知识证明都能变成签名(zk-SNARK 的基础);⚠️坑:哈希必须同时含承诺 t 和消息 m,漏掉 t = "weak Fiat–Shamir",2023 年仍在多个 zk 库中被发现。Schnorr 优势:可证明安全、⭐线性结构支持多签聚合(比特币 Taproot 的动机)、签名短。选型:⭐Ed25519 首选(64B、确定性、无 k 风险)、BLS 可聚合(一千个签名合成一个)、ML-DSA 抗量子。⚠️协议层三问题:重放(要序号/时间戳)、⭐上下文混淆(防御是域分隔:签名前加 "myapp:v1:transfer:" 前缀;真实事故是钱包用同一密钥签登录和转账)、密钥复用。

下一节 👉 18-PKI与证书.md

打卡记录保存在你的浏览器里,首页能看到总进度