基于哈希函数的签名

回首近几年,我有幸经历了两个相互冲突、却又令人着迷的时代潮流变迁。第一个潮流变迁是:专家学者们耗费四十年设计的密码学,终于派上用场;从信息加密电话安全、到加密数字货币,我们可以在生活的方方面面发现使用密码学的例子。

第二个潮流变迁是:所有密码学家已经做好准备,迎接以上美好的幻灭

正文开始之前我得重申一下,本文所讲的不是所谓量子计算启示录(末日预言),也不是要讲 21 世纪密码学的成功。我们要谈论的是另一件未成定局的事情——密码学有史以来最简单的(也是最酷炫的)技术之一:基于散列函数的签名。

1.png

在 20 世纪 70 年代末,Leslie Lamport 发明了基于哈希函数(Hash Function,又称散列函数)的签名 ,并经过 Ralph Merkle 等人进一步改进。而后的很多年,这被视为密码学领域一滩有趣的“死水”,因为除了相应地产生冗长的(对比其他复杂方案)签名,基于哈希函数的签名好像没有什么作用。然而近几年来,这项技术似乎有了复苏的迹象。这很大程度归因于它的特性——不同于其他基于RSA或离散对数假设的签名,哈希函数签名被视为可以抵抗量子计算攻击(如 Shor's 算法)。

首先,我们进行一些背景介绍。

背景:哈希函数和签名方法

在正式介绍哈希函数签名之前,首先你得知道密码学中的哈希函数是什么。哈希函数可以接受一串字符(任意长度)作为输入,经过“消化”后,产生固定长度的输出。常见的密码学哈希运算,像是 SHA2SHA3 或 Blake2 等,经运算会产生长度介于 256 ~ 512 位的输出。

一个函数 H(.) 要被称作“密码学”哈希函数,必须满足一些安全性的要求。这些要求有很多,不过我们主要聚焦在以下三个方面:

  1. 抗-原像攻击 Pre-image resistance (或俗称“单向性”):给定输出 Y=H(X),想要找到对应的输入 X 使得 H(X)=Y 是一件“极度费时”的工作。(这里当然存在许多例外,但最棒的部分在于,不论 X 属于什么分布,找到 X 的时间成本和暴力搜寻相同。)
  2. 抗-次原像攻击:这和前者有些微的差别。给定输入 X,对于攻击者来说,要找到另一个 X' 使得 H(X)=H(X') 是非常困难的。
  3. 抗-碰撞:很难找到两个输入 X1, X2,使得 H(X1)=H(X2)。要注意的是,这个假设的条件比 抗-次原像攻击还要严苛。因为攻击者可以从无垠的选择中寻找任意两个输入。

我们相信所有本文提到的哈希函数示例都能提供上述的所有特性。换言之,没有任何可行的(甚至是概念上的)方法能破解它。当然这种情况也是会变的,如果破解的方法被找到,我们当然会立即停用哈希函数(稍后会讨论关于量子计算攻击的特例)。

我们的目标是使用哈希函数构造数字签名方案,因此简要回顾数字签名这个词能带来很大的帮助。

数字签名方法源于公钥的使用,使用者(签署人)生成一对密钥:公钥和私钥。使用者自行保管私钥,并能够用私钥“签署”任何消息,从而产生相应的数字签名。任何一个持有公钥的人都能验证该消息正确性和相关签名。

扫描二维码关注公众号,回复: 6229177 查看本文章

从安全的角度来说,我们希望签名是不可伪造的,或是说“存在不可伪造性”。这意味着攻击者(没有私钥控制权的人)无法在某段消息上伪造你的签名。有关数字签名安全的更多定义请参阅这里

Lamport 一次性签名

在 1979 年,一位名叫 Leslie Lamport 的数学家发明了世界上第一个基于哈希函数的签名。Lamport 发现只要使用简单的哈希函数,或是单向函数,就可以构建出非常强大的数字签名方法。

强大的前提是,用户只需要做一次签名的动作就能保证安全性!后续会做更详细的阐述。

为了更好的讨论,我们假设以下条件:一个哈希函数,它能接受 256 位的输入并产生 256 位的输出; SHA256 哈希函数就是个绝佳的示范工具;我们也需要能产生随机输入的方法。

假设我们的目标是对 256 位的消息进行签名。要得到我们需要的密钥,首先需要生成随机的 512 个位字符串,每个位字符串长度为 256 位。为了便于理解,我们将这些字串列为两个独立的表,并以符号代指:

sk0= sk10, sk20, ...,sk2560
sk1= sk11, sk21, ...,sk2561

我们以列表 (sk~0~, sk~1~) 表示用来签名的 密钥。接下来为了生成公钥,我们将随机的位字符串通过 H(.) 进行哈希运算,得到公钥如下表:

pk0= H(sk10), H(sk20), ...,H(sk2560)
pk1= H(sk11), H(sk21), ...,H(sk2561)

现在我们可以将公钥 (pk~0~,pk~1~) 公布给所有人知道。比如说,我们可以把公钥发给朋友,嵌入证书中,或是发布在 Keybase 上。

接着我们使用密钥对 256 位消息 M 进行签名。首先我们得将消息 M 重现为独立的 256 位元(Bit,又称“比特”):

M1, M2, ..., M256 ∈ {0, 1}

签名算法的其余部分非常简单。我们从消息 M 的第 1 位至第 256 位,逐一相应在密钥列表中的其中一个密钥上取出字符串。而所选密钥取决于我们要签名的消息每一位(bit)的值。

具体一点地说,对于 i = [1,256],如果第 i 位的消息位元 Mi = 0,我们会从 sk0 表中选择第 i 个字符 (ski0) ,作为我们签名的一部分;如果第 i 位的消息位元 Mi = 1,我们则从 sk1 表进行前述过程(即,如果我们要对消息 M 中的第 3 位进行签名,而该位值为 0,则使用 sk0 中的第三位,sk03,作为我们签名的一部分)。对每个消息位元完成此操作后,我们将选中的字符串连接,得到签名。

过程如图示说明,因为部分过程化简,密钥和消息长度只有 8 个 bit(位元)。要注意的是,每个色块代表的都是不同的随机 256 位字符串。

2.png

当某个用户(已经知道公钥 (pk0, pk1))收到消息 M 和签名,她能够轻易地验证这个签名。我们以 si 表示签名中第 i 个组成部分,用户能够检查相应的消息 Mi 并计算哈希值 H(si) 。如果 Mi = 0 ,则哈希值必须匹配公钥 pk0 中的元素;如果 Mi = 1 ,则哈希值必须匹配公钥 pk1 中的元素。

如果签名中的每个元素经过哈希运算后,都能找到对应的正确部分的公钥,我们就会说这个签名是有效的。以下是验证过程图示,签名中至少有一个签名元素:

3.png

如果你开始觉得 Lamport 的计划有些疯狂,你既是对的,也是错的。

首先探讨下这个数字签名方法的弊端。我们会发现, Lamport 方法的签名和密钥实在太大了,大约有数千 bits。而且更要命的是,这个方法存在严重的安全局限:每个密钥只能被用来签名一个消息,所以 Lamport 方法作为“一次性签名” 在这里被拿来举例。

这种安全局限为什么存在呢?回想一下, Lamport 签名表明了在各个消息位元上可能的两个密钥之一。假如只需要签署一条信息,这个签名方法完全没问题。然而,如果我签署了两条在每一个对应位置 i 的 bit 值都不同的消息,然后连同密钥一起发送出去,这可能导致大问题!

假设攻击者从不同的消息得到两个有效的签名,她便能够发起 “混合搭配(mix and match)”攻击,成功伪造签署第三条我从未签名过的信息。以下图示说明这个攻击过程:

4.png

这个问题的严重程度取决于你签名的消息的相异程度,以及有多少消息被攻击者给截获了。但总的来说,这肯定不是件好事。

让我们总结一下 Lamport 签名方法;它很简单、快速,但它在实际应用上还有很多不足之处。或许我们可以做一点优化?

从一次性签名到多次签名:基于默克尔树 (Merkle's tree) 的签名

Lamport 签名方法是个好的开端,但是无法用单一密钥签名多条信息,是它最大的弊端。Martin Hellman 的学生 Ralph Merkle 由此得到大量启发,他很快地想到了一个聪明的解决办法。

虽然我们不打算在这里展开解释默克尔方法的步骤,我们还是来试着理清 Ralph 的想法。

我们现在的目标是用 Lamport 签名方法签署 N 条信息。最直观的方法是,以最初的 Lamport 方法生成 N 个不同的密钥对,然后将所有公钥关联起来,集合成一个超巨大的 mega-key。(mega-key是我现编的术语。)

如果签名者继续拿着这么一把密钥集合,她就可以对 N 条不同消息进行签名,严格上来讲这也只是一把 Lamport 密钥。看起来,这样就解决了密钥重用的问题。验证者也有对应的公钥能够验证所有收到的消息。没有任何的 Lamport 密钥被使用两次。

很明显的,这种方法很糟糕,因为时间成本太高了。

具体地说,上述这种天真的方法中,为了达到要求的签名次数,签名者必须分发比普通 Lamport 公钥还要大数倍的公钥(签名者还要继续拿着同样巨大的私钥)。人们很可能会对这种结果感到不满,也会反思有没有办法避免这种负作用产生。接下来,让我们进入 Merkle 方法。

Merkle 方法希望能找到一个能签署多条不同消息的方法,同时避免公钥的成本线性激增。Merkle 方法的实现如下:

  1. 首先,生成 N 个独立的 Lamport 密钥,我们以 (PK1, SK1), ..., (PKN, SKN) 表示之。
  2. 接下来,将每一个公钥分别放到 Merkle hash tree (见下图),并计算根节点哈希值。这个根节点就会成为Merkle签名方法中的 “主公钥”。
  3. 签名者报关全部的 Lamport 密钥(公钥和私钥),用于签名。

关于 Merkle tree 的更多描述请点击这里。概略地说,Merkle 方法提供了一种能收集不同的值,并用一个 “根” 哈希(例子中使用的哈希函数,长度为 256 bits)代表所收集的值的方法。给出这个根哈希,就能简单“证明” 某个元素存在于这个给出的哈希树。而且这个证明的大小和叶节点数量成对数关系。

5.png

-Merkle tree,来自维基百科的解释。Lamport 公钥被放进叶节点中,然后根节点成为主公钥。-

要签名的时候,签名者从 Merkle tree 中直接选择公钥,并用对应的 Lamport 密钥签名。接着她将得到的签名结果连接 Lamport 公钥并附上“Merkle 证明”。Merkle root 可以来佐证该默克尔树中包含选中的公钥(即整个方法使用的公钥)。最后签名者将整个集合当作消息签名发送出去。

(验证者只要直接将这个“签名”分别解压为 Lamport 签名、 Lamport 公钥、 Merkle 证明,就能进行验证。验证者能够依靠拿到的 Lamport 公钥验证 Lamport 签名,并用 Merkle 证明这把公钥的确存在于 Merkle tree 中。只要满足这三个条件,验证者就能确信签名是有效的。)

这个方法的缺点是会将“签名”大小增加两倍以上。不过,现在 Merkle 方法主要的公钥只是一串简单的哈希值,使得这个方法比上面提到的原始 Lamport 方法更为简洁。

最后还有个优化部分,密码学强度的伪随机数发生器能够输出生成各式各样的密钥,同时“压缩”密钥数据本身。这使得原先庞大的位元(显然是随机的)能够转换为简短的“种子(seed)”。

很赞啦!

让签名和密钥更有效率一点

Merkle 方法使得一次性签名转变为 N 次性签名。构造这种方法仍然需要基于某些一次性签名方法,比如 Lamport 方法;但不幸的是,Lamport 方法的(带宽)成本仍相对高昂。

有两种主要的方法可以降低这些成本。第一种也是 Merkle 提出的;为了更好的解释许多强大的签名方法,我们优先说明这项技术。

回想一下 Lamport 方法,要对一条 256 位的消息进行签名,我们需要一个包含 512 个独立密钥(和公钥)位串的向量,签名本身就是 256 个密钥位串的集合。(这些数字会被需要签名的消息位元激活,位元可以是 "0" 或 "1" ,因此需要从两张不同的密钥表中提取适合的密钥元素。 )

这里引发了新的思考:如果我们不对所有的消息位元进行签名,会怎么样呢?

更详细点说,在 Lamport 方法中,我们通过输出密钥位串对一条消息的每个位元进行签名——无论它的值是什么。如果我们不要同时签名一条消息中 0 和 1 的位元,而是只签名 1 的位元,那又会如何呢?这么做能够将公钥和私钥的大小减半,因为我们可以完全丢掉整条 sk0 列。

现在我们只有单一列位串的密钥 sk1,...,sk256,对消息的每个位元 Mi = 1我们都会输出一个字符串 ski;对于消息的每个位元 Mi = 0我们都会输出......无(因为许多消息都会包含很多的 0 位元,这么做能缩减签名大小,这些 0 位元将不再带来任何成本)。

这种方法的明显缺陷是:它极度不安全,所以请不要这么做!

举例来说,假设有个攻击者观察到一条已经被签名的消息,消息开头是“1111...”。现在攻击者想要在不破坏签名的情况下,将消息编辑成“0000...”,只需要删掉这条签名中的几个组成部分即可!简言之,虽然要将 0 位元“翻转” 成 1 位元很困难,但反之要将 1 换成 0 就非常简单了。

现在有了个解决办法,而且它非常巧妙。

让我们接着瞧瞧。虽然无法避免攻击者将消息中的 1 改成 0 ,但我们能发现这些改动。只要将一个简单的“校验和(checksum)”附加到消息上,然后将消息和校验和一起签名。对于签名验证者来说,她必须验证整份签名的两个值,也需要确定收到的校验和是正确的。

我们使用的校验和非常小:它由简单的二进制整数组成,表示原始消息中的所有 0 位元数。

如果攻击者试图修改消息内容(或是校验和),使得部分 1 位元变成 0 位元,并没有手段可以阻止她。但是这种攻击会增加消息中的 0 位元数,这会使得校验和无效,验证者从而会拒绝这个签名。

当然,机智的攻击者可能还会试图混淆校验和(校验和也和消息一起被签名),增加校验和的整数值来匹配她篡改的位元数。然而最关键的是,因为校验和是二进制整数,如果要增加校验和的值,攻击者势必得将一些 0 位元转换成 1 位元。又因为校验和也被签过名,这种签名方法从源头阻止这种转换(将 0 换成 1),因此攻击者无法得逞。

6.png

(如果你继续记录下去,的确会增加被签名的“消息”的大小。在我们的例子中,一条 256 位的消息的校验和,需要额外的 8 位元及增加相应的签名成本。不过,如果这条消息包含许多 0 位元,这么做对于缩减签名大小仍然非常有效。)

Winternitz 方法:时间换取空间

上述的小窍门可以将公钥集合的大小减半,并以相同量级缩减签名的大小。这样做很棒,但称不上什么创新之举。密钥和签名仍然长达数千个位元。

如果我们能再加大力度缩减这些数字,那就更完美了。

我们采纳的最后优化手段,是由 Robert Winternitz 基于上述 Merkle 方法所提出的更进一步升级。在实际使用中,这个方法缩减了 4~8 倍的签名和公钥大小——代价是增加了签名和验证的时间。

Winternitz 的想法源自一项技术:时空权衡(time-space tradeoff)。这类解决方案使得空间需求减小,代价是增加计算时间(反之亦然)。以下几个思考能帮助我i们更好的理解 Winternitz 方法:

如果我们今天不要签名消息的每一位元(0 或 1),而是将它们视为更大的消息编码,那会怎么样呢?比如我们每 4 个位元签名一次,或是每八个位元(1 字节)签名一次?

在最初的 Lamport 方法中,我们有两列字符串作为签名密钥(和公钥)的一部分。一列是签署 0 位元用的,另一列签署 1 位元。

现在假设我们想直接对字节签名,而不是每个位元做签名。最直接的做法是将密钥(和公钥)列表从原来的两个增加到 256 个——合成一张大表,涵盖消息中每一个可能的字节。这样签名者就能从一张巨大的密钥表中选取合适的值,每次针对整个字节作签名。

不幸的是,这个主意烂透了。虽然它将签名的大小减少了 8 倍,付出代价却是将公钥和密钥大小增加 256倍 。如果这份巨大的密钥表能够用于多次签名那也就算了——但它不能。当密钥表发生重用时,这种“签整个字节版本”的 Lamport 方法也会遇到和原始 Lamport 方法一样的限制。

上面描述的种种问题,最终引出了 Winternitz 方法。

存储和分发 256 个随机密钥列成本非常高昂,如果我们只在需要签名的时候,以编程的手段生成需要的密钥呢?

Winternitz 的想法是,先替初始密钥生成一列随机种子 sk0 = (sk10, ..., sk2560) 。接着他提出用哈希函数 H() 对初始密钥的每个元素进行运算得出下一列密钥:sk1 = (sk11, ..., sk2561) = [ H(sk10), ..., H(sk2560) ],而不是直接随机生成其他列。以此类推,可以继续使用哈希函数求得下一列 sk2。

这么做的好处是我们只需要储存一列密钥,当需要签名时再使用哈希运算生成其他密钥即可。

但公钥部分怎么办呢?这就是 Winternitz 聪明的地方!

具体来说,Winternitz 提出可以对最后一列密钥再次进行哈希散列运算,生成一列公钥: pk 。(在实际应用中,我们只需要 255 列密钥,因为最后一列密钥我们可以直接视为公钥。)这个方法的优雅之处在于,任何一个给定的密钥值都能通过公钥检查;只要持续进行哈希散列运算,然后查看是否得到最终公钥即可。

完整的密钥生成过程如下:

7.png

注意:要对字节进行签名,我们只需要 255 个密钥列,而不是 256 个。因为最后一个密钥列等同于公钥。

要对消息的第一个字节进行签名,我们需要从合适的列中选取一个值。举例来说,如果是字节 “0” ,我们将从 sk0 中输出一个值作为我们的签名;如果是字节 “20” ,我们将从 sk20 中输出一个值作为我们的签名。对于最后一个字节 “255” ,我们虽然没有对应的密钥列,但也不要紧!我们可以输出空字串或是输出 pk 中的元素。

要注意,实际上我们不需要保存每一个密钥列。通过计算推演,我们能从初始列得到所需密钥。验证者只需要拿好公钥并直接进行适当次数(视消息字节数情况而定)的哈希运算,就能够验证计算结果是否等同于正确的公钥元素。

如同前面章节讨论的 Merkle 优化方法,到目前为止所描述的 Winternitz 方法也有着明显的漏洞。因为密钥列彼此关联(即 sk1 = H(sk0)),任何人看到字节“0”及其签名,都能够轻易的将消息的字节改为 “1”,然后更新签名匹配篡改(同理可类推)。事实上,无论什么字节攻击者都能够添加到消息中。如果没有检查机制,这会导致非常严重的伪造攻击隐患。

解决这个问题的办法正如前面所提到的,如果要防止攻击者修改签名,签名者必须计算原始消息字节的校验和,并对校验和也进行签名。校验和的设计使得攻击者添加任何字节,都会使校验和失效。这里不做过多讨论,详细请参阅这里

毫无疑问,校验和正确与否是至关重要的;只要有任何一点纰漏,都会为你带来很不好的影响。如果在生产环境中部署这样的签名,会造成严重的后果。

拿下面这个有点粗糙的图说明,对一条 4 字节的消息使用 Winternitz 签名方法:

8.png

注意:示例中的消息由字节 byte 组成而不是 bit 。虽然我很确定正确地计算了校验和,但因为是手算的,有时候可能会有点小纰漏。

基于哈希函数的签名究竟有什么优势?

通篇讨论中,我们主要在讲基于哈希散列的签名如何运作的,而没有谈到为什么选择它。现在就让我们说明一下这种结构的签名特点是什么。

早期支持散列签名的观点认为,这种方法非常快速而且简单,因为这种方法只需要评估合适的哈希函数,并进行一些数据拷贝即可。纯粹从计算成本角度考虑,哈希散列签名绝对有能力和 ECDSA 、RSA 等一较高下,同时对于轻量级设备非常友好。当然,这种效率在很大程度上是以牺牲带宽为代价的。

不过(最近)关于哈希散列签名的兴起有着更复杂的原因:所有的公钥加密即将被破解

更具体地说:即将问世的量子计算机,几乎会对所有目前使用的签名方法的安全性造成巨大的冲击,包含 RSA、ECDSA 等等。因为 Shor 算法(以及它的变体)让我们能在多项式时间内,解决离散对数和因式分解问题的方法,这使得绝大多数签名方法不再安全可靠。

大部分哈希散列签名不容易受到 Shor 算法影响。当然,我们不是说哈希散列签名能够完全抵抗量子计算攻击;对哈希运算最有效的量子攻击称作 Grover 算法,它会大大降低哈希运算的安全性 。不过这种程度的安全性影响,远小于 Shor 算法带来的影响(破解时间层级差别在平方到立方之间),因此可以简单通过增加哈希函数的运算内容和输出大小,来保障签名的安全性。像是 SHA3 系列哈希函数开发目的很明确,它能处理更大的输入,并有更好的能力对抗量子计算攻击。

至少从理论上来讲,哈希散列签名有趣之处在于它留给我们一线机会,抵御未来的量子计算攻击——或许就只能挣扎一下,谁知道呢。

未来展望

提醒一下,目前我们谈论的哈希散列签名都是“古董级”的,上述所有的哈希散列签名方法都发明于 1970 年或是早于 1980 年,这似乎不适用于今时今日。

我写完这篇文章初稿后,有许多人要我多讲讲这个领域近几年的发展。我无法给出详尽的列表,不过我能够稍微描述近几年出现的一些点子(感谢 Zooko 和 Claudio Orlandi)。

无状态签名。上述所有方法共通的一个局限在于,它们要求签名者在签名之间保持状态。对于一次性签名我们可以很直观的了解:我们必须避免重复使用任何密钥;而在 Merkle 多次签名中,我们必须记住正在使用的叶节点公钥,避免重复使用。更糟的是,Merkle 方法要求签名者先构建所有可能用上的密钥对,所以签名的数量是有限的。

在 1980 年,Oded Goldreich 指出有一种手段能够建立无需保持状态的签名 。想法如下:不预先生成所有密钥,而是生成一个简短的一次性公钥的“验证树”。每一个密钥的都可以在树的底层签署额外的一次性公钥,并以此类推。如果使用单个种子生成所有私钥,则表示完整的 Merkle 树不需要在密钥生成时存在,而可以在生成新密钥时按需求构建。每个签名都包含签名和公钥的“验证链”;从根节点开始,一直到叶节点真正用于签名的密钥对。

这项技术让我们能在非常“深”的 Merkle 树构建大量(指数级)的密钥。这允许我们构造非常多的一次性公钥,只需要我们随机地(或伪随机地)选择签名密钥,则发生密钥重用的可能性极低。当然,这是直觉想法。有关这个想法的更多优化和具体示例,请参考 Bernstein 等人的 SPHINCS 提案;SPHINCS-256 实例提供大小约为 41KB 的签名。

Picnic:后量子零知识签名(post-quantum zero-knowledge based signatures)。Picnic 是完全不同的想法,它基于一项全新的非交互式零知识证明系统(non-interactive zero-knowledge proof system) 技术,称为 ZKBoo。ZKBoo 是一种新的 ZK 证明系统,它基于一种称为“头脑中的 MPC”的技术,让证明者使用多方计算来进行自证 。这已经过于复杂,无法详细解释;但最终的结果是,人们可以继续使用哈希函数来验证复杂的语句。

简而言之,Picnic 和 ZK 证明系统提供除了哈希函数签名之外的第二种思考方向。这些签名的成本仍然很高 ——高达几百千字节,但是技术演进可以大大缩减签名的量级。

结语:老套的安全提醒

如果你稍微回想一下本文,我们费了番功夫描述一些哈希函数的安全特性。这可不只是简单展示而已!你可以看到,哈希散列签名的安全性,完全取决于我们所选择的哈希函数。

(再暗示一下,哈希散列签名不安全的地方,就是攻击者已经在设法攻破的哈希函数)

大多数讨论哈希散列签名的基础的文章,通常会在哈希函数的抗原像攻击上提出安全疑虑。以 Lamport 签名为例,我们能够很直观的理解。给定一个公钥,如果攻击者能够计算出哈希运算的输入,那她就能够轻易伪造一个有效签名。这种攻击使得签名不再安全。

不过,这是攻击者只看到了公钥,却还未看到任何一个有效签名的情况。在下面情况中,攻击者能够获得更多讯息。假设她现在有了公钥和一部分的密钥 pk01 = H(sk01) 和 sk01。如果攻击者能够找到公钥 pk01 的次原像,虽然她还不能对不同的消息进行签名伪造,但实际上她已经生成了一个新的签名。对于签名安全要求特别特别严格的场景(SUF-CMA),这就可以被视为一次有效的攻击了。因此 SUF-CMA 在抗 次原像攻击上有很高的标准。

最后一个问题出现在哈希散列签名方案的大多数实际应用中。你会注意到上面的描述假定我们正在签署 256 位消息。但在实际应用中,许多消息大于 256 位。因此,大多数人使用哈希函数 H() 前,会先散列消息 D = H(M),然后再对结果值 D 进行签名,而不是直接对消息签名。

不过这会导致最后这里要提到的攻击。因为这个做法的不可伪造能力,取决于该哈希函数的抗碰撞能力。攻击者能够找到两个不同的消息 M1 ≠ M2 ,而 H(M1) = H(M2),这就表示她找到了对两条不同消息都有效的签名。这导致了 EUF-CMA 安全性的微小缺陷。


链接: https://blog.cryptographyengineering.com/2018/04/07/hash-based-signatures-an-illustrated-primer/


猜你喜欢

转载自blog.csdn.net/shangsongwww/article/details/90178487
今日推荐