维基百科对数字签名的定义为:
数字签名 是一种数学方案,用于验证数字消息或文档的真实性。有效的数字签名可以给接收者信心,表明消息来自接收者所知的发件人。
— 维基百科
他们还提供了一个生成和验证数字签名的过程图:
来源:https://commons.m.wikimedia.org/wiki/File:Private_key_signing.svg#mw-jump-to-license(CC-BY-SA)
爱丽丝使用她的私钥对消息进行签名,然后鲍勃可以用她的公钥验证该消息确实是来自爱丽丝,并且没有被篡改。这看起来非常简单直接,也是大多数开发人员对签名的理解以及其用途的看法。这也导致了签名在各种场合中的广泛使用:比如验证软件更新、认证 SSL 连接等等。
但加密专家们对数字签名的理解则有所不同,这种更为高级的理解方式能够告诉我们什么是适当的或者不适当的用例。
身份验证协议
有许多方法可以构建安全的签名方案。尽管你可能会立即想到 RSA,但密码学家最喜爱的方案或许是 Schnorr 签名。这些构成了现代 EdDSA 签名的基础,在经过大量修改后也成为了 DSA/ECDSA。
Schnorr 签名的故事并不始于一个签名方案,反而始于一个交互式的身份验证协议。一个身份验证协议是证明你是谁(“证明者”)的一种方式,它需要某项验证服务(“验证者”)。例如登录一个网站。需要注意的是,该协议只关心证实你的身份,而不是建立一个安全会话之类的事情。
有许多不同的方式来实现这一目标,如发送用户名和密码或者类似 WebAuthn/passkeys 的方法(这里有个我们稍后会提到的讽刺提法)。其中一种特别优雅的协议称为 Schnorr 协议。它之所以优雅是因为其实现简单,并且基于广泛接受的基本安全性假设,还有一些不错的特性我们会在稍后提及。
协议的基本结构分为三个阶段:承诺-挑战-响应。如果你对挑战-响应认证协议有所了解,这只是在其基础上增加了一条初始的承诺消息。
爱丽丝想要向鲍勃证明自己的身份。爱丽丝已经拥有一个长期私钥 a,相应的鲍勃已经收到了对应的公钥 A。这些密钥是在 Diffie-Hellman 类似的有限域或椭圆曲线群中计算得出的,我们可以表示 A = g^a mod p 其中 g 是生成元而 p 是群的素数模。协议的工作过程如下:
- 爱丽丝生成随机 临时密钥 r 及其对应的公钥 R = g^r mod p。她将 R 发送给鲍勃作为 承诺 信息。
- 鲍勃存储 R 并生成一个随机 挑战值 c,并将该值发送给爱丽丝。
- 爱丽丝计算 s = ac + r 并将其回传给鲍勃作为 响应 信息。
- 最终,鲍勃检查是否 g^s = A^c * R (mod p)。如果成立,则爱丽丝成功通过了身份验证,否则就是冒名顶替者。为什么会这样工作是因为 g^s = g^(ac + r) 且 A^c * R = (g^a)^c * g^r = g^(ac + r) 也是正确的。为什么它是安全的则是另一个话题了。
如果你不清楚所有细节也不用担心,我可能会在某个时候写一篇关于 Schnorr 身份验证的博客文章,但网上有很多解释,现在只需要接受这是一个安全的身份验证方案。它也有一些很好的特性。
其中一个特性是它是一个(诚实验证者)零知识证明(对私钥的知识)。这意味着观察者即使看着爱丽丝进行身份验证以及验证者自己也无法从中获取任何关于爱丽丝私钥的信息,但是验证者仍然确信爱丽丝知道那个私钥。
这是因为可以通过简单的反向操作为任何私钥轻松创建有效运行的日志,从响应出发并计算出能匹配该响应的挑战和承诺。任何人都可以在不需要了解任何私钥的情况下做到这一点。也就是说对于任何给定的挑战,都可以找到一个易于计算正确响应的承诺。(但他们不能在发送了一个承诺之后回答随机挑战)。因此它们在观看的真实互动中不会获得任何信息。
Fiat-Shamir
那么这个身份验证协议与数字签名有什么关系呢?答案是一个叫做 Fiat-Shamir 启示的过程,可以自动将某些交互式身份验证协议转换成非交互式签名方案。不是所有协议都能进行这样的转换,只有那些具有一定结构的协议可以,但 Schnorr 身份验证协议满足条件。由此产生的签名方案即为Schnorr 签名方案。
令人欣慰的是,Fiat-Shamir 转换非常简单。我们基本上只是用加密散列函数替换了协议中的 挑战 部分,该函数对要签名的消息和承诺公钥进行哈希运算:c = H(R, m)。
就这样。签名仅仅是这对 (R, s)。
请注意,鲍勃不再参与此过程,爱丽丝自己就可以完成。为了验证签名,鲍勃或其他任何人重新计算 c (通过哈希消息和 R)然后按身份验证协议执行验证步骤。
以此构建成的 Schnorr 签名是安全的(假如添加了一些关键的安全措施!),并且效率很高。EdDSA 签名方案本质上是在做一些调整后的现代版 Schnorr 方案。
关于签名适当用途的启示
我是按照在密码学教科书中常见的形式来介绍 Schnorr 签名和 Fiat-Shamir 的。我们先从一个身份验证协议开始,进行简单的转换,最终变成了一个安全的签名方案。皆大欢喜!这些教科书通常接下来会讲到签名的多种用法而不再提及身份验证协议。但这种转换并不是完全积极的过程:在这个过程中失去了很多!
交互式身份验证协议中的许多有用方面在签名方案中都丢失了:
- 协议运行仅对互动双方(爱丽丝和鲍勃)有意义,相比之下,一个签名对所有人同样有效。
- 协议运行具有针对性时间点的意义。爱丽丝始终是对鲍勃发出的具体挑战作出回应。一个签名可以在任何时候得到验证。
这些似乎都是签名方案的优点,但实际上却是在很多情况下是缺点。签名单常常用于身份验证,但实际上我们需要的是与特定交互相关的验证。签名缺乏上下文性正是为什么 JWT 标准必须添加明确声明,如 audience 和 issuer 检查,以确保 JWT 来自预期来源并到达预期