[博客翻译]数字签名及其风险规避方法


原文地址:https://neilmadden.blog/2024/09/18/digital-signatures-and-how-to-avoid-them/


维基百科对数字签名的定义为:

数字签名 是一种数学方案,用于验证数字消息或文档的真实性。有效的数字签名可以给接收者信心,表明消息来自接收者所知的发件人。

— 维基百科

他们还提供了一个生成和验证数字签名的过程图:

来源: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 是群的素数模。协议的工作过程如下:

  1. 爱丽丝生成随机 临时密钥 r 及其对应的公钥 R = g^r mod p。她将 R 发送给鲍勃作为 承诺 信息。
  2. 鲍勃存储 R 并生成一个随机 挑战值 c,并将该值发送给爱丽丝。
  3. 爱丽丝计算 s = ac + r 并将其回传给鲍勃作为 响应 信息。
  4. 最终,鲍勃检查是否 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 标准必须添加明确声明,如 audienceissuer 检查,以确保 JWT 来自预期来源并到达预期目的地,并包含 过期 信息或唯一标识符(必须记住)以防止重放攻击。相当一部分实际中的 JWT 漏洞是由于开发者忘记执行这种检查造成的。

WebAuthn 是这种情况下的另一个例子。理论上它是一个教科书般典型的身份验证协议,但由于它建立在数字签名之上,因此必须像 JWT 一样添加大量的“上下绑定”信息。颇具讽刺意味的是,最常用的 WebAuthn 签名算法 ECDSA 本身就是一个类似于 Schnorr 的方案。

TLS 也将签名用于本质上属于身份验证协议的情况,并由于缺少足够的上下文绑定信息而导致了一系列漏洞。 (SSL 也将签名用于验证证书,我认为在这种场景下是技术的合理应用。证书正好是一种你希望将交互协议转化为非交互形式的情况。但另一方面我们 也在 这种场景中使用交互协议 (DNS):耸肩)。

总而言之,数字签名的实际用途中有很大一部分其实是某种形式的身份验证方案,使用实际的身份验证方案会更合适。但这并不意味着使用类似 Schnorr 协议的东西!实际上有更好替代方式,我会在最后再讨论这个问题。

特殊完备性:设计的脆弱性

在探讨替代方案之前,我想指出目前几乎所有签名方案在实践中都非常脆弱。Schnorr 识别的零知识安全性基于其具有一个称为 特殊完备性 的属性。特殊完备性基本表明,如果爱丽丝无意中在同一协议的两次运行中使用了相同的承诺 (R),那么任何观察者都可以恢复她的私钥。

这听起来是一个极其脆弱的概念,将其构建到安全协议中实在是不可思议!如果我不小心重用了这个随机值,那岂不是泄露了我的整个私钥?事实确实如此:在实际部署的签名系统中,这种nonce重用漏洞非常普遍,并且已经导致了许多私钥被破解(例如 PlayStation 3、各种比特币钱包等)。

尽管其脆弱性,但这种特殊的有效性观念对于许多签名系统的安全性而言仍然是至关重要的。它们确实是一种被诅咒的技术!

为了解决这个问题,一些实现和新标准如EdDSA使用确定性承诺,这些承诺基于私钥和消息的哈希。这确保了即使出现相同的承诺也是因为消息完全相同,从而防止了私钥被恢复。遗憾的是,这类方案后来被发现更易受到故障注入攻击(一种扩展性较低或较为特定的攻击向量),因此现在出现了“对冲”方案,在哈希中重新注入一些随机性。这是层层叠加的被诅咒的技术。

如果你的答案是回归传统的RSA签名,请不要被愚弄。使用传统方法也存在着许多陷阱,但这要留待另一篇文章讨论。

您想要不可否认性吗?

签名引起问题的另一种方式是它们针对其所解决的任务来说过于强大。你仅仅想验证一封电子邮件是否来自合法的服务器,而现在你却提供了泄露私人通信来源的无可反驳的证据。糟糕!

签名为加密原语提供了锤子一样的功能。除了进行消息认证之外,它们还提供了第三方可验证性和(部分)不可否认性

你不必明确地需要匿名或不可追溯性也可以理解,这些强安全属性会带来破坏性和无法预见的副作用。在开放系统中,默认情况下不应该有不可否认性。

我可以继续讲下去。从基本上没有任何可用的后量子签名方案(所有方案都太庞大或者太冒险),到非规范签名和共因子的问题,等等。签名方案的问题似乎永无止境。

要用什么替代呢?

好吧,既然签名这么差劲,那么我能用什么呢?

首先,如果你能够用简单的共享密钥方案如HMAC来解决问题,那就这么做。与公钥加密相比,HMAC可能是迄今为止发明的最健壮的加密原语。你得非常刻意才能搞砸HMAC。(我的意思是,还有时序攻击,以及Bouncy Castle混淆比特和字节并使用16位HMAC密钥的情况,因此仍然需要注意一点……)

如果你需要公钥加密,那么……仍然使用HMAC。使用带X25519的身份验证密钥封装机制生成一个共享密钥,并用它与HMAC一起认证你的消息。这实质上是公钥认证加密但不包括实际加密。(有些人错误地称这些方案为指定验证者签名,但实际上并不是如此)。

签名在软件或固件更新方面效果良好,但在其他几乎所有方面都非常糟糕。