1 本章概要
针对计算机所处理的消息,有时候我们也需要用到“指纹”。当需要比较两条消息是否一致时,我们不必直接对比消息本身的内容,只要对比它们的“指纹”即可。
本章中,使用单向散列函数就可以获取消息的“指纹”,通过对比“指纹”,就能够知道两条消息是否一致。
我们将详细介绍现在使用非常广泛的 SHA-1 单向散列函数,并思考对单向散列函数的攻击方法。
2 什么是单向散列函数
2.1 这个文件是不是真的呢
Alice 终于完成了一个软件开发,接下来只要把文件从 Alice 的硬盘中拷贝到 CD 上就可以了。不过,把文件写到 CD 上非常耗时, Alice 已经很累了,她决定今天晚上早上回家休息,明天再继续弄。
第二天, Alice 来到公司准备把文件写入 CD ,但她忽然产生了这样的疑问:
“这个文件和我昨天晚上生成的文件是一样的吗?”
Alice 的疑问是这样的——会不会有人操作 Alice 的计算机,将文件改写了呢?也有可能通过网络入侵 Alice 的计算机。或者,也许 Alice 的计算机感染了病毒,造成文件被篡改……在这里,我们姑且把篡改文件的这个主体称为“主动攻击者 Mallory”。总而言之,Alice 需要知道从昨天到今天这段时间内, Mallor有 是否篡改了文件的内容。
也就是说,Alice 需要确定自己的文件的完整性(integrity)。
稍微想一想我们就能找一种确认文件完整性的简单方法——在回家之前先把文件拷贝到一个安全的地方保存,第二天在用这个文件工作之前,先将其和事先保存的文件进行对比就可以了。如果两者一致,那就说明文件没有被篡改。
不过,下图这种确认完整性的方法,其实是毫无意义的。因为如果可以事先把文件保存在一个安全的地方,那根本就不需要确认完整性,直接用事先保存的文件来工作不就行了吗?
这里还存在一个效率问题。如果需 需要确认完整性的文件非常巨大,那么文件的拷贝、保存以及比较都将非常耗时。
我们能不能获取到 Alice 所生成的文件的“指纹”呢?如果我们不需要对整个巨大的文件进行对比,只需要对比一个较小的指纹就能够检查完整性的话,那该多方便:
本章要介绍的单向散列函数,就是一种采集文件指纹的技术。单向散列函数所生成的散列值,就相当于消息的指纹。
2.2 什么是单向散列函数
单向散列函数(one-way hash function)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hash value)。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。
这里的消息可以是任何形式的消息,图像、声音、视频。因为无论任何消息,单向散列函数都会将它作为单纯的比特序列来处理,即根据比特序列计算出散列值。
散列值的长度和消息的长度无关。以 SHA-1 单向散列函数为例,它所计算出的散列值的长度永远是 160 比特(20字节)。
由于散列值很短,因此很容易处理和使用。
回家之前,Alice 用单向散列函数计算文件的散列值:
35 36 37 38 39 A1 D2 F4 J5 5B 9J 35 36 37 38 39 A1 D2 F4 J5
单向散列函数所输出的散列值的长度是固定的(在这个例子中是 20 字节),无论 Alice 的文件大小是多大,散列值永远都是 20 字节(160比特)。Alice 可以将这个值打印出来,保存,或者拿回家藏在枕头下面~
第二天早上,Alice 再次计算硬盘中文件的散列值,如果再次计算出的散列值和昨晚的散列值相同,就可以判断这个文件是真的,否则就是不一样的。
2.3 单向散列函数的性质
1. 根据任意长度的消息计算出固定长度的散列值
2.能够快速计算出散列值
3.消息不同散列值也不同
如果单向散列函数计算出的散列值没有发生变化,那么消息很容易就会被篡改,这个单向散列函数也就无法被用于完整性的检查。两个不同的消息产生同一个散列值的情况称为碰撞(collision)。如果要将单向散列函数用于完整性的检查,则需要确保在事实上不可能被认为地发现碰撞。
难以发现碰撞的性质称为抗碰撞性(collision resistance)。密码技术中所使用的单向散列函数,都需要具备抗碰撞性。
我们以 Alice 用单向散列函数来检查文件完整性的场景为例,现在,我们假设 Alice 所使用的单向散列函数不具备抗碰撞性。
Alice 在回家之前得到了散列值,Alice 在睡觉的时候,Mallory 入侵了 Alice 的计算机,并改写了 Alice 的文件。
由于假设 Alice 的单向散列函数不具备抗碰撞性,因此 Mallory 能够找到一种改写文件的方法,使得改写后文件的散列值不会发生变化,因此 Alice 将 Mallory 改写后的文件写入了 CD。
这里所说的抗碰撞性,指的是难以找到另外一条具备特定散列值的消息。当给定某条消息的散列值时,单向散列函数必须确保要找到和该条消息具有相同散列值的另外一条消息是非常困难的。这一性质称为弱抗碰撞性。单向散列函数都必须具备弱抗碰撞性。
和弱抗碰撞性相对的,还有强抗碰撞性。所谓强抗碰撞性,是指要找到散列值相同的两条不同的消息是非常困难的这一性质。在这里,散列值可以是任意值。
密码技术中所使用的单向散列函数,不仅要具备弱抗碰撞性,还必须具备强抗碰撞性。
4. 具备单向性
2.4 关于术语
单向散列函数也称为消息摘要函数(message digest function)、哈希函数或者杂凑函数。
输入单向散列函数的消息也称为原像(pre-image)。
单向散列函数输出的散列值也称为消息摘要(message digest)或者指纹(fingerprint)**。
完整性也称为一致性。
“散列”的英文 “hash” 一词,原意是“斧子”,后来被引申为“剁碎的肉末”。单向散列函数的作用,实际上就是将很长的消息剁碎,然后再混合成固定长度的散列值。
3 单向散列函数的实际应用
3.1 检测软件是否被篡改
3.2 基于口令的加密
单向散列函数也被用于基于口令的加密(Password Based Encryption,PBE)。
PBE 的原理是将口令和盐(salt,通过伪随机数生成器产生的随机值)混合后计算起散列值,然后将这个散列值用作加密的秘钥。通过这样的方法能够防御针对口令的字典攻击,将在第十一章详解。
3.3 消息认证码
使用单向散列函数可以构造消息认证码。
消息认证码是将“发送者和消息接收者之间的共享秘钥”和“消息”进行混合后计算出的散列值,使用消息认证码可以检测并防止通信过程中的错误、篡改以及伪装。
消息认证码在 SSL/TLS 中也得到了运用,将在第十四章详解。
3.4 数字签名
数字签名是现实社会中的签名(sign)和盖章这样的行为在数字世界中的实现。数字签名的处理过程非常耗时,因此一般不会对整个消息内容直接施加数字签名,而是先通过单向散列函数计算出消息的散列值,然后再对这个散列值施加数字签名,将在第九章详解。
3.5 伪随机数生成器
使用单向散列函数可以构造伪随机数生成器。
密码技术中所使用的随机数需要具备“事实上不可能根据过去的随机数列预测未来的随机数列”这样的性质。为了保证不可预测性,可以利用单向散列函数的单向性,将在第十二章详解。
3.6 一次性口令
一次性口令(one-time password),经常被用于服务器对客户端的合法性认证,在这种方式中,通过使用单向散列函数可以保证口令只在通信链路上传送一次(one-time),因此即使窃听者窃取了口令,也无法使用。
4 单向散列函数的具体例子
4.1 MD4、MD5
MD4 是由 Rivest 于 1990 年设计的单向散列函数,能够产生 128 比特的散列值,由于寻找到了 MD4 散列碰撞的方法,因此现在它已经不安全了。
MD5 是由 Rivest 于 1991 年设计的单向散列函数,能够产生 128 比特的散列值,由于 MD5 的强抗碰撞性已经被攻破,也就是说,现在已经能够产生具有相同散列值的两条不同的消息,因此它也已经不安全了。
MD 是消息摘要(Message Digest)的缩写。
4.2 SHA-1、SHA-256、SHA-384、SHA-512
SHA-1 是由 NIST(National Institute of Standards and Technology,美国国家标准技术研究所)设计的一种能够产生 160 比特的散列值的单向散列函数。
SHA-256、SHA-384、SHA-512 都是由 NIST 设计的单向散列函数,它们的散列值长度分别为 256 比特、384 比特、512 比特。统称为 SHA-2。
它们的消息长度都存在上限。SHA-1 的强抗碰撞性已于 2005 年被攻破,也就是说,现在已经能够产生具备相同散列值的两条不同的消息。不过,SHA-2 还尚未被攻破。
4.3 RIPEMD-160
RIPEMD-160 是 1996 年设计的一种能够产生 160 比特的散列值的单向散列函数。RIPEMD 的强抗碰撞性已于 2004 年被攻破,但 RIPEMD-160 还尚未被攻破。
4.4 AHS(Advanced Hash Standard)与 SHA-3
在 2005 年 SHA-1 的强抗碰撞性被攻破的背景下, NIST 开始着手制定用于取代 SHA-1 的下一代单向散列函数 SHA-3 。SHA-3 和 AES 一样采用公开竞赛的方式进行标准化。
5 单向散列函数 SHA-1
此章主要讲解其具体算法,有感兴趣者请看原书。
6 对单向散列函数的攻击
6.1 暴力破解(攻击故事 1 )
Alice 在计算机上写了一份合同。晚上,攻击者 Mallory 入侵了计算机,他想将其中的:
Alice 要支付的金额为 100 万元。
改成:
Alice 要支付的金额为 1 亿元。
不过,不仅要修改合同内容,还要不能改变散列值。
Mallory 可以从文档文件所具有的冗余性入手。所谓文档文件的冗余性,是指在不改变文档意思的前提下能够对文件的内容进行修改的程度。
举个例子,下面的这些句子基本上说的都是一个意思:
Alice 要支付的金额为 1 亿元。
Alice 要支付的金额为壹亿元。
Alice 要支付的金额为 100000000 元。
Alice 应支付 1 亿元。
作为报酬, Alice 需要支付 1 亿元。
除此之外,还有一些通过机器来进行修改的方法。例如,可以在文件的末尾添加 1 个、2 个、3 个甚至更多的空格,或者还可以对文档中的每一个字稍微改变一些颜色,这都不会影响文档的意思。在这里需要注意的是,即便我们对文件所进行的修改是无法被人类察觉的,但只要是对文件进行了修改,单向散列函数就会产生不同的散列值。
于是,Mallory 利用文档的冗余性,通过机器生成了一大推“支付一亿元的合同”。如果在这一大推合同中,能够找到一个合同和 Alice 原本的“ 100 万元合同”恰好产生相同的散列值,那 Mallory 就算是成功了。
在这里,Mallory 所进行的攻击就是暴力攻击。正如对密码可以进行暴力破解一样,对单向散列函数也可以进行暴力破解。这相当于一种试图破解单向散列函数的“弱抗碰撞性”的攻击。在这种情况下,暴力破解需要尝试的次数可以根据散列值得长度计算出来。以 SHA-1 为例,由于它的散列值长度为 160 比特,因此最多只要尝试 2160 次就能够找到目标消息了。(这里不懂为什么是 2160 次)
6.2 生日攻击(攻击故事 2 )
编写合同的人不是 Alice 而是主动攻击者 Mallory 。 Mallory 事先准备两份具备相同散列值的“100 万元合同”和“一亿元合同”,然后将“100 万元合同”交给 Alice 让她计算散列值。随后, Mallory 再像故事 1 中一样, 掉包合同。
这里 Mallory 所进行的攻击不是寻找生成特定散列值的消息,而是要找到散列值相同的两条消息,而散列值可以是任意值。这样的攻击,一般称为生日攻击(birthday attack),这是一种试图破解单向散列函数的“强抗碰撞性”的攻击。
这里存在一个生日驳论的数学思想,有兴趣的请自行谷歌。
7 单向散列函数无法解决的问题
假如,攻击者 Mallory 伪装成 Alice ,向 Bob 同时发送了消息和散列值。Bob 通过单向散列函数检查消息的完整性,但是无法检查出发送者的身份是否被 Mallory 进行了伪装。也就是说,单向散列函数能够辨别出“篡改”,但无法辨别出“伪装”。
因此我们还需要进行认证,用于认证的技术包括消息认证码和数字签名。消息认证码能够向通信对象保证消息没有被篡改,而数字签名不仅能够向通信对象保证消息没有被篡改,还能够向所有第三方作出这样的保证。
认证需要使用秘钥,也就是通过对消息附加 Alice 的秘钥(只有 Alice 才知道的密码信息)来确保消息真的属于 Alice。
8 本章小结
本章学习了用于确认消息完整性的单向散列函数,其能够根据任意长度的消息计算出固定长度的散列值,通过对比散列值就可以判断两条消息是否一致。这种技术对辨别篡改非常有效。
以及学习了代表性的单向散列函数——SHA-1的实现方法以及破解方法——暴力破解和生日攻击。
但是,单向散列函数,虽然可以辨别出篡改,但无法辨别伪装。要解决这个问题,我们需要消息验证码和数字签名。将在下一章介绍消息验证码。
9 小测验
- MD5 是一种能够将任意长度的数据转换为 128 比特的对称密码算法。
- 要找出和某条消息具备相同散列值的另一条消息是非常困难的。
- 要找出具有相同散列值但互不相同的两条消息是非常困难的。
- SHA-1 的散列值长度为 20字节。
- 如果消息仅被改写了 1 比特,则散列值也仅发生 1 比特的改变。