回首近几年,我有幸经历了两个相互冲突、却又使人着迷的时代潮流变迁。第一个潮流变迁是:专家学者们耗费四十年设计的密码学,终于派上用场;从信息加密、电话安全、到加密数字货币,咱们能够在生活的方方面面发现使用密码学的例子。算法
第二个潮流变迁是:全部密码学家已经作好准备,迎接以上美好的幻灭。数组
正文开始以前我得重申一下,本文所讲的不是所谓量子计算启示录(末日预言),也不是要讲 21 世纪密码学的成功。咱们要谈论的是另外一件未成定局的事情——密码学有史以来最简单的(也是最酷炫的)技术之一:基于散列函数的签名。安全
在 20 世纪 70 年代末,Leslie Lamport 发明了基于哈希函数(Hash Function,又称散列函数)的签名 ,并通过 Ralph Merkle 等人进一步改进。然后的不少年,这被视为密码学领域一滩有趣的“死水”,由于除了相应地产生冗长的(对比其余复杂方案)签名,基于哈希函数的签名好像没有什么做用。然而近几年来,这项技术彷佛有了复苏的迹象。这很大程度归因于它的特性——不一样于其余基于RSA或离散对数假设的签名,哈希函数签名被视为能够抵抗量子计算攻击(如 Shor’s 算法)。函数
首先,咱们进行一些背景介绍。工具
在正式介绍哈希函数签名以前,首先你得知道密码学中的哈希函数是什么。哈希函数能够接受一串字符(任意长度)做为输入,通过“消化”后,产生固定长度的输出。常见的密码学哈希运算,像是 SHA二、SHA3 或 Blake2 等,经运算会产生长度介于 256 ~ 512 位的输出。优化
一个函数 H(.) 要被称做“密码学”哈希函数,必须知足一些安全性的要求。这些要求有不少,不过咱们主要聚焦在如下三个方面:加密
咱们相信全部本文提到的哈希函数示例都能提供上述的全部特性。换言之,没有任何可行的(甚至是概念上的)方法能破解它。固然这种状况也是会变的,若是破解的方法被找到,咱们固然会当即停用哈希函数(稍后会讨论关于量子计算攻击的特例)。翻译
咱们的目标是使用哈希函数构造数字签名方案,所以简要回顾数字签名这个词能带来很大的帮助。设计
数字签名方法源于公钥的使用,使用者(签署人)生成一对密钥:公钥和私钥。使用者自行保管私钥,并可以用私钥“签署”任何消息,从而产生相应的数字签名。任何一个持有公钥的人都能验证该消息正确性和相关签名。cdn
从安全的角度来讲,咱们但愿签名是不可伪造的,或是说“存在不可伪造性”。这意味着攻击者(没有私钥控制权的人)没法在某段消息上伪造你的签名。有关数字签名安全的更多定义请参阅这里。
在 1979 年,一位名叫 Leslie Lamport 的数学家发明了世界上第一个基于哈希函数的签名。Lamport 发现只要使用简单的哈希函数,或是单向函数,就能够构建出很是强大的数字签名方法。
强大的前提是,用户只须要作一次签名的动做就能保证安全性!后续会作更详细的阐述。
为了更好的讨论,咱们假设如下条件:一个哈希函数,它能接受 256 位的输入并产生 256 位的输出; SHA256 哈希函数就是个绝佳的示范工具;咱们也须要能产生随机输入的方法。
假设咱们的目标是对 256 位的消息进行签名。要获得咱们须要的密钥,首先须要生成随机的 512 个位字符串,每一个位字符串长度为 256 位。为了便于理解,咱们将这些字串列为两个独立的表,并以符号代指:
sk0= sk10, sk20, …,sk2560
sk1= sk11, sk21, …,sk2561
咱们以列表 (sk~0~, sk~1~) 表示用来签名的
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 位字符串。
当某个用户(已经知道公钥 (pk0, pk1))收到消息 M 和签名,她可以轻易地验证这个签名。咱们以 si 表示签名中第 i 个组成部分,用户可以检查相应的消息 Mi 并计算哈希值 H(si) 。若是 Mi = 0 ,则哈希值必须匹配公钥 pk0 中的元素;若是 Mi = 1 ,则哈希值必须匹配公钥 pk1 中的元素。
若是签名中的每一个元素通过哈希运算后,都能找到对应的正确部分的公钥,咱们就会说这个签名是有效的。如下是验证过程图示,签名中至少有一个签名元素:
若是你开始以为 Lamport 的计划有些疯狂,你既是对的,也是错的。
首先探讨下这个数字签名方法的弊端。咱们会发现, Lamport 方法的签名和密钥实在太大了,大约有数千 bits。并且更要命的是,这个方法存在严重的安全局限:每一个密钥只能被用来签名一个消息,因此 Lamport 方法做为“一次性签名” 在这里被拿来举例。
这种安全局限为何存在呢?回想一下, Lamport 签名代表了在各个消息位元上可能的两个密钥之一。假如只须要签署一条信息,这个签名方法彻底没问题。然而,若是我签署了两条在每个对应位置 i 的 bit 值都不一样的消息,而后连同密钥一块儿发送出去,这可能致使大问题!
假设攻击者从不一样的消息获得两个有效的签名,她便可以发起 “混合搭配(mix and match)”攻击,成功伪造签署第三条我从未签名过的信息。如下图示说明这个攻击过程:
这个问题的严重程度取决于你签名的消息的相异程度,以及有多少消息被攻击者给截获了。但总的来讲,这确定不是件好事。
让咱们总结一下 Lamport 签名方法;它很简单、快速,但它在实际应用上还有不少不足之处。或许咱们能够作一点优化?
Lamport 签名方法是个好的开端,可是没法用单一密钥签名多条信息,是它最大的弊端。Martin Hellman 的学生 Ralph Merkle 由此获得大量启发,他很快地想到了一个聪明的解决办法。
虽然咱们不打算在这里展开解释默克尔方法的步骤,咱们仍是来试着理清 Ralph 的想法。
咱们如今的目标是用 Lamport 签名方法签署 N 条信息。最直观的方法是,以最初的 Lamport 方法生成 N 个不一样的密钥对,而后将全部公钥关联起来,集合成一个超巨大的 mega-key。(mega-key是我现编的术语。)
若是签名者继续拿着这么一把密钥集合,她就能够对 N 条不一样消息进行签名,严格上来说这也只是一把 Lamport 密钥。看起来,这样就解决了密钥重用的问题。验证者也有对应的公钥可以验证全部收到的消息。没有任何的 Lamport 密钥被使用两次。
很明显的,这种方法很糟糕,由于时间成本过高了。
具体地说,上述这种天真的方法中,为了达到要求的签名次数,签名者必须分发比普通 Lamport 公钥还要大数倍的公钥(签名者还要继续拿着一样巨大的私钥)。人们极可能会对这种结果感到不满,也会反思有没有办法避免这种负做用产生。接下来,让咱们进入 Merkle 方法。
Merkle 方法但愿能找到一个能签署多条不一样消息的方法,同时避免公钥的成本线性激增。Merkle 方法的实现以下:
关于 Merkle tree 的更多描述请点击这里。概略地说,Merkle 方法提供了一种能收集不一样的值,并用一个 “根” 哈希(例子中使用的哈希函数,长度为 256 bits)表明所收集的值的方法。给出这个根哈希,就能简单“证实” 某个元素存在于这个给出的哈希树。并且这个证实的大小和叶节点数量成对数关系。
–
要签名的时候,签名者从 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),所以攻击者没法得逞。
(若是你继续记录下去,的确会增长被签名的“消息”的大小。在咱们的例子中,一条 256 位的消息的校验和,须要额外的 8 位元及增长相应的签名成本。不过,若是这条消息包含许多 0 位元,这么作对于缩减签名大小仍然很是有效。)
原文连接: https://blog.cryptographyengineering.com/2018/04/07/hash-based-signatures-an-illustrated-primer/
做者: Matthew Green
翻译&校对: Ian Liu & 阿剑
以太中文网经受权转载