跳转至

05 如何有效避免长度延展攻击?

你好,我是范学雷。

上一讲,我们列举了常见的单向散列函数,我们还知道了退役的、遗留的和现行的算法,通过对处理能力限制和算法的性能的讨论,我们对如何选择哈希算法有了更明确的认知。

还记得我们留了一个小尾巴吗?我们提到了“长度延展攻击”。“长度延展攻击”是怎么一回事?我们为什么要了解它?在单向散列函数的使用上,我们需要注意哪些安全问题?

这就是我们这一次要解决的事情。

什么是长度延展攻击?

我们先来看看什么是“长度延展”,这样会有利于你理解“长度延展攻击”。

现在,假设我们有两段数据,S和M,以及一个单向散列函数h。如果我们要把这两段数据合并起来,并且还要计算合并后的散列值,这就叫做单向散列函数的长度延展。

不过,问题来了,是S放在前面(h(S|M)),还是M放在前面(h(M|S))?既然,我们说,散列值是无法预测的,那么,数据编排的顺序有意义吗?

如果S和M都是公开的信息,顺序是不重要的。可如果S是机密信息,M是公开信息,这两段数据的排列顺序就至关重要了。如果机密信息放在了前面,就存在“长度延展攻击”的风险

弄清楚了长度延展,长度延展攻击就很好理解了,就是说我们可以利用已知数据的散列值,计算原数据外加一段延展数据后的散列值。也就是说,如果我们知道了h(S|M),我们就可以计算h(S|M|N)。其中,数据N就是原数据追加的延展数据。

如果S和M都是公开的信息,能够计算延展数据的散列值也没什么紧要的。但是,如果S是机密数据,它的用途一般就和机密有点关系。比如说,因为没有人知道我拥有的机密数据S,所以,当我给定一段公开信息M后,只有我自己才能计算S和M的散列值。

通过验证S和M的散列值,我就知道一个给定散列值是我计算、派发出去的,还是别人伪造的。

比如下面的这段数据:

key_id=44fefa051fc1c61f5e76f27e620f51d5&perms=read&hash_sig=38d39516d896f879d403bd327a932d9e

其中,key_id表示机密数据的编号,perms表示操作权限,hash_sig是使用机密数据key对perms的签名。签名的计算,就是使用单向散列函数:

sig = h(key|perms)

由于使用了机密数据key,按照设想,这段数据只能由机密数据的持有者生成,然后分发出去,供授权的人使用。机密数据的持有者接收到这样的数据后,重新计算数据签名,然后对比请求数据里的签名。如果两个签名相同,就表示这是一个自己生成的、合法的授权,就可以授予请求数据所要求的权利。

不过,这个设计就存在“长度延展攻击”的风险。攻击者并不需要知道机密数据,就可以通过一个已知的URL,构造出一个新的合法的URL,从而获得不同的授权。

伪造的数据看起来像下面的样子:

key_id=44fefa051fc1c61f5e76f27e620f51d5&perms=read\0x80\0x00...\0x02&delete&hash_sig=a8e6b9704f1da6ae779ad481c4c165a3

在这段伪造的数据中,0x80到0x02之间的数据是数据块补齐数据,而且新添加了删除的权限,并且重新计算、替换了数据签名。

其中,数据签名需要使用机密数据,而攻击者并不知道机密数据,那么攻击者怎样伪造数据签名呢?要解决这个疑问,我们需要先看看单向散列函数的构造。

我们在上一讲简单地提到过,一起来重新回顾一下。一个典型的单向散列函数,应该由四个部分组成:数据分组、链接模式、压缩函数和终结函数。

我们之前着重说了数据分组,我们现在来看看其他的部分:

  • 压缩函数是单向函数,负责着算法的单向性要求;
  • 终结函数不是单向函数,负责着整理压缩函数的输出,形成散列值的任务;
  • 链接模式,负责把下一个数据分组和上一个压缩函数的输出结果结合起来,确保算法的雪崩效应能够延续。

值得一提的是,在MD5,SHA-1,SHA-256和SHA-512的算法设计中,终结函数就是把压缩函数的输出向量排列成一个字节串。知道了字节串,我们也就知道了压缩函数的输出向量。

压缩函数接收一个数据分组和上一个压缩函数的运算结果。如果知道了上一个压缩函数的运算结果,我们就能够计算下一个分组数据的压缩函数运算结果。这里,就是出现安全漏洞的地方

我们把原来的散列值作为压缩函数的一个输入,我们再按照数据补齐规范,去补齐原来数据到数据分组的整数倍,然后加入新的数据,我们就可以计算原数据和扩展数据的散列值了。

新的散列值的计算,不需要知道预先设想的机密数据。但是整个散列值的计算,又的确使用了机密数据。只不过,这个计算过程需要两个部分,第一部分由机密数据的持有者计算,第二部分是攻击者使用第一部分的结果,伪造了一个使用了机密数据的散列值。

但是,如果我们把数据编排顺序换一下,把公开信息M放在前面,机密信息S放在后面,长度延展攻击就不起作用了。这就是数据编排顺序对数据安全性的影响。

怎么有效避免长度延展攻击?

一个单向散列函数,只要使用了类似上述的压缩函数和链接模式,都是“长度延展攻击”的可疑对象。我们上一次提到的MD2、MD5、SHA-0、SHA-1、SHA-2,都有长度延展攻击的风险。其中,对于下列算法,长度延展攻击是完全有效的:

  • MD2
  • MD5
  • SHA-0
  • SHA-1
  • SHA-256
  • SHA-512

对于下列算法,长度延展攻击虽然不是完全有效,但是算法的安全级别显著降低了:

  • SHA-224
  • SHA-384

对于下列算法,长度延展攻击没有效果(包括所有的SHA-3算法):

  • SHA-512/224
  • SHA-512/256
  • SHA-3

上面这么长的列表,你是不是觉得好多,有点烦?其实,我们讨论长度延展攻击,目的不是让你记住上述的列表。

我们要从中学会、理解一个实用的经验:不要单纯使用单向散列函数来处理既包含机密信息、又包含公开信息的数据。即使我们把机密信息放在最后处理,这种使用方式也不省心。

如果我们需要使用机密数据产生数据的签名,我们应该使用设计好的、经过验证的算法,比如我们后面会讨论的消息验证码(Message Authentication Code)和基于单向散列函数的消息验证码(Hash-based Message Authentication Code)

另外,如果需要设计算法,我们还要理解另外一个实用的原则:算法要皮实、耐用,不能有意无意地用错了就有安全漏洞。你看,SHA-1和SHA-2已经很简单、皮实了,用错了场景还是有严重的问题。相比之下,SHA-3同样简单,但是更皮实。

这和我们在《代码精进之路》的专栏里反复讨论的API要简单、直观、皮实,是一个道理。

既然我们不能单纯地使用单向散列函数处理混合了机密信息和公开信息的数据。那我们能不能单纯地使用机密信息,或者单纯地使用公开信息?回答这个问题,还要看具体的使用场景。

有哪些典型的适用场景?

我们已经知道了,单向散列函数是密码学的核心。下面是一些典型的使用单向散列函数的场景:

  • 校验数据完整性;
  • 数字签名,和非对称密钥及其算法结合使用;
  • 消息验证码,和对称密钥及其算法结合使用;
  • 生成伪随机数;
  • 生成对称密钥。

还记得我们在之前,讨论过了怎么使用单向散列函数校验数据完整性。

输入:
    1、数据D
    2、原始数据的散列值H
    3、计算散列值使用的散列函数
输出:
    数据D是不是完整的?


运算:
    1、使用散列函数计算数据D的散列值H';
    2、对比数据的散列值H和计算获得的散列值,如果两个散列值相同,则数据D是完整的;否则,数据D是修改过的数据。

如果我们单纯地使用单向散列函数校验数据完整性,是要对比数据的散列值的。既然是对比,也就意味着有两个散列值。这时候,我们需要考虑的主要问题就是:给定的散列值有没有被更改?

散列值的计算是公开的,给定一段数据,谁都可以计算它的散列值。如果数据可以被修改,而且给定的散列值也是修改后的数据的散列值,这个数据完整性校验是没有意义的。

所以,单纯使用单向散列函数去校验数据的完整性,我们需要确保给定的散列值是不能被修改的,这就是这个使用场景的限制。

其余的单向散列函数的使用场景,我们后面还会接着讨论。

Take Away(今日收获)

今天,我们讨论了单向散列函数的长度延展攻击,以及使用单向散列函数需要注意的事项,还列举了典型的单向散列函数使用场景。

通过今天的讨论,我们要:

  • 知道单向散列函数存在长度延展攻击;
  • 了解避免长度延展攻击的办法;
  • 尽量不要单纯使用单向散列函数来处理包含机密信息的数据。

另外,今天也是单向散列函数这一模块的最后一讲了。我们也来小结一下这一模块要注意的知识点,拉个清单。

在这一模块里,我们要掌握下面的基本概念和最佳实践:

  1. 知道单向散列函数的三个特点:正向计算容易,逆向计算困难,散列值长度固定。
  2. 如果散列值不能被恶意修改,单向散列函数可以用来解决数据完整性问题。
  3. 知道有退役的算法、遗留的算法和现行的算法,并且不要使用退役的算法,尽快升级遗留的算法。
  4. 了解密码学算法常用的三个推荐系统,美国的 NIST德国的 BSI和欧洲的 ECRYPT-CSA,要养成定期查看推荐指标的习惯,跟得上密码学的进展。
  5. 知道安全强度,以及现在要使用128位的安全强度的密码学算法,长期系统要考虑使用256位的密码学算法。
  6. 知道要尽量选用现行的、流行的算法。对于单向散列函数来说,它们是SHA-256,SHA-384和 SHA-512。
  7. 尽量不要单纯使用单向散列函数来处理包含机密信息的数据,如果不得已,要尽量避免长度延展攻击。

思考题

好的,又到了留思考题的时间了。

今天的思考题是一个拓展题,你要自己去发现单向散列函数的更多适用场景。

我们一直强调,使用单向散列函数校验数据完整性,需要保证原始的散列值不能被更改。你能不能找到一些场景,可以让我们不用担心原始的散列值被更改,单纯使用单向散列函数就可以校验数据完整性?

除了我们上面列出来的一些场景,你能不能找出更多的单向散列函数使用场景?比如说,利用散列值长度固定的特点,利用碰撞困难的特点?

欢迎在留言区留言,记录、讨论你发现的新使用场景。

好的,今天就这样,我们下次再聊。

精选留言(15)
  • zzzz 👍(16) 💬(1)

    老师,我可能理解的特别不好,但是找了好多资料依然不能理解这个“长度延展攻击”,反而越找越乱,所以只能来留言问您了,希望您还能回复。 1,什么是机密信息啊?机密信息是只有写机密信息的人才能用的机密信息吗?机密信息的作用是什么呢? 2,M是一般信息,S表示机密信息,为什么我们额外加的数据N只能在M后面而不能在S后面呢?无论S和M如何组合,不都是信息吗?我们把S和M的任意排列认为是data的话,按照图里说的,hash(hash(data)+自己加的数)=hash(data+自己加的数) 这个公式都是成立的啊,所以S和M如何排序重要吗? 3 ,长度延展攻击到底想做什么啊,只是得到hash(data+自己加的数) 有什么作用吗? 老师,我看大家都没有问过我说的这些问题,所以可能我理解的特别特别不好。但是我也真的认真看了,也对不懂的地方查了很多很多资料,还是不明白,希望老师能够帮助我,谢谢老师!

    2021-01-18

  • Lorin 👍(3) 💬(1)

    老师,我有两个问题: 1、长度延展攻击中,补齐数据是怎么获取到的啊? 2、长度延展攻击后,原散列值和扩展数据的散列值肯定不一样,这样的话,接收者都已经知道收到的数据与原散列值不一致了,这个时候长度延展攻击有什么意义呢?

    2020-12-02

  • Reol 👍(2) 💬(1)

    老师我还有个小问题,发送者是否要公开自己的加密算法? 按理说没有公开加密算法的必要,那么接收者不知道加密算法什么都做不了,不知道分组大小和压缩函数的固定处理位数,无法反推每个分组的输出向量,也不知道补充数据的格式。最重要的是没有算法无法自己计算延长数据的散列值。

    2021-08-06

  • qinsi 👍(2) 💬(2)

    疑问:补齐数据应该是由数据长度决定的,那么在不知道机密数据 S 的长度的情况下,攻击者要如何知道该补多长的数据?跟 S 在前或在后有关吗?

    2020-12-02

  • Reol 👍(1) 💬(1)

    老师我有两个小问题,求解答!!!!!! 假设:S私密,M公开,N延长 1. 关于长度延展的理解:对于散列值 h(S|M),因为可以根据终结函数的顺序输出,来推断出每个原文分组的压缩函数的输出。所以延长实际上的操作是,推断提取出散列值 h(S|M) 中最后一个分组的压缩函数输出向量,将其作为初始向量来运算散列值 h(N),然后直接拼接两个散列值 h(S|M) + h(N) = h(S|M|N),结果就是消息S|M|N的散列值 ,这样理解对吗? 2. 按照我对专栏原文还有配图的理解,在延长攻击中原文 S|M 后面要加上补充数据 (假设为m),然后再加上N和其补充数据 n。这样得出的 h(S|M|N) 实际上是 h(S|M|m|N) 对应的原文应该是 S|M|m|N 而不是 S|M|N,所以验证时是把原文 S|M|Bm|N 和 h(S|M|m|N) 发给S的持有者来验证。但这样S的持有者获得的原文里面M和N中间多了一段意义不明的m,S的持有者不会觉得奇怪吗?

    2021-08-06

  • godliness 👍(0) 💬(1)

    你能不能找到一些场景,可以让我们不用担心原始的散列值被更改,单纯使用单向散列函数就可以校验数据完整性? 老师,TLS 中的数字签名算不算呢?

    2023-10-25

  • godliness 👍(0) 💬(1)

    老师,上面 S 和 M 在防伪领域是不是经常用到?

    2023-10-24

  • Ankhetsin 👍(0) 💬(1)

    老师,md6和国密sm系列算法怎么样?

    2022-01-08

  • 木头发芽 👍(0) 💬(1)

    引用问题: "散列值的计算是公开的,给定一段数据,谁都可以计算它的散列值。如果数据可以被修改,而且给定的散列值也是修改后的数据的散列值,这个数据完整性校验是没有意义的。" 方案: 给数据+盐后计算散列值, 这个盐是一定规则生成不在网络上传输的,如果攻击者替换了数据和散列值,但因为不知道盐值,跟验证者生成的散列值不一致,就知道数据被串改, 该方案是否可行?

    2021-05-21

  • bugs 👍(0) 💬(2)

    老师好, 散列算法的数据分组是连序性的吗?如果是的话,链接模式能保证靠前的数据块修改后的雪崩效应,不能保证靠后数据块修改的雪崩效应。

    2021-03-30

  • zzzz 👍(0) 💬(1)

    老师,追写一下我自己的思路,如果前一条问题太多太···不知如何回答,老师看看我对上面的问题的理解对不对: 首先,长度拓展攻击的目的就是,根据已知的hash(A),得到hash(A+B),其中,B是自己随意加的数据 机密数据S就相当于一个身份,有机密数据的人就想相当于有这个身份,,这个人传递的信息就是hash(S+M),其中S表示机密数据身份,M表示他想传递的信息。比如这个人是一个长官,S就是他的长官身份,M就是他传递的命令,这个hash值就传给他的下属。但是现在有一个人想要冒充长官给下属下发命令N,于是他就把自己想要追加的命令放在原来的S+M后面,构成S+M+N。下属们收到hash之后,因为这个hash依然是有S的,所以下属们以为这个就是他们长官的命令,所以连M和N这两个命令都执行了。所以长度有效攻击的作用就是冒充身份,让自己的hash里带上S 对吗老师

    2021-01-18

  • LXX 👍(0) 💬(2)

    老师好,把原来的散列值作为压缩函数的一个输入,再按照数据补齐规范,去补齐原来数据到数据分组的整数倍,然后加入新的数据,我们就可以计算原数据和扩展数据的散列值了,这块不是很理解,可以详细讲述一下吗?我不是很清楚为什么通过这个方式就可以计算原数据和扩展数据的散列值,或者有什么文章可以推荐借鉴理解的。谢谢老师

    2021-01-11

  • wrzgeek 👍(0) 💬(1)

    是不是可以这样理解,无论是(M|S)还是(S|M)结构,延展数据N都是要紧跟着M的,也就是延展后的数据结构是(M|N|S)或者(S|M|N),这样的话,对于S在后边的结构,就无法进行延展攻击了。

    2020-12-10

  • Litt1eQ 👍(0) 💬(1)

    单纯使用单向散列函数就可以直接校验数据的完整性,我想到了存储在数据库中的密码,通常情况下存到数据库中的密码是不会直接存储明文的,一般都是把用户的原始密码经过散列之后保存,这样我们校验用户输入的密码经过散列之后和数据库存贮的散列值进行比较,感觉并没有考虑到数据库当中的散列值被修改的情况。 我记得在区块链中,好像是利用散列计算的困难性,找到一个某个满足要求的散列值。

    2020-12-02

  • qinsi 👍(0) 💬(2)

    感谢解答。看来猜测数据长度是需要一个Oracle存在的(比如服务端返回请求是否合法)。下一个问题是,假设已知数据的长度,那么无论机密数据 S 在前或在后,应该都能得到补齐数据才对,为什么说放在后面的时候攻击就不起作用了呢?

    2020-12-02