一、长度延展攻击
我们先来看看什么是长度延展,这样有利于你理解长度延展攻击
假设我们现在有两段数据 S 和 M,以及一个单向散列函数 h,我们把这两段数据合并起来,再计算合并后的散列值,这就是单向散列函数的长度延展
于是,问题来了,是 S 放在前面再计算 h(SM),还是 M 放在前面再计算 h(MS)?既然,我们说散列值是无法预测的,那么,数据的先后排列顺序还有意义吗?
如果 S 和 M 都是公开信息,顺序并不重要,可如果 S 是机密信息,M 是公开信息,那么,这两段数据的先后排列顺序就至关重要了,因为如果机密信息在前,就会有“长度延展攻击”的风险
弄清楚了长度延展,长度延展攻击就很好理解了,我们可以利用已知数据的散列值,计算原数据外加一段延展数据后的散列值,也就是说,如果我们知道了 h(SM),我们就可以计算 h(SMN),其中,N 就是在原数据基础上追加的延展数据
如果 S 和 M 都是公开信息,即便计算出延展数据的散列值也没什么要紧的,但是,如果 S 是机密数据,比如说,因为没有人知道我拥有的机密数据 S,所以,当我给定一段公开信息 M 后,只有我自己才能计算 SM 的散列值,通过验证 SM 的散列值,我就知道一个给定的散列值是我计算、派发出去的,还是别人伪造的
比如下面的这段数据:
key_id=44fefa051fc1c61f5e76f27e620f51d5&perms=read&hash_sig=38d39516d896f879d403bd327a932d9e
其中,key_id 表示机密数据的编号,perms 表示操作权限,hash_sig 是使用机密数据 key 对 perms 的签名,签名的计算使用的是单向散列函数,即 hash_sig = h(key|perms)
由于使用了机密数据 key,按照设想,这段数据只能由机密数据的持有者(请求者)生成,然后派发出去,供授权的人使用,机密数据的持有者(授权响应者)接收到这样的数据后,重新计算数据签名,然后对比请求数据里的签名,如果两个签名相同,表示这是一个合法的请求,就可以授予请求的权限
不过,这个设计有“长度延展攻击”的风险,攻击者并不需要知道机密数据,就可以通过一个已知的 URL,构造出一个新的合法的 URL,从而获得不同的授权,伪造的数据看起来像下面的样子:
key_id=44fefa051fc1c61f5e76f27e620f51d5&perms=read\0x80\0x00...\0x02&delete&hash_sig=a8e6b9704f1da6ae779ad481c4c165a3
在这段伪造的数据中,0x80 到 0x02 之间的数据是数据块补齐数据,并且新添加了删除权限,而且重新计算、替换了数据签名
其中,数据签名需要使用机密数据,而攻击者并不知道机密数据,那么攻击者如何伪造数据签名呢?要解决这个疑问,我们需要先看看单向散列函数的构造,一个典型的单向散列函数,应该由四个部分组成:数据分组、链接模式、压缩函数和终结函数
二、如何有效避免长度延展攻击
三、示例
#include <stdio.h>
#include <openssl/md5.h>
int main(int argc, const char *argv[])
{
MD5_CTX c;
unsigned char buffer[MD5_DIGEST_LENGTH];
MD5_CTX c2;
unsigned char buffer2[MD5_DIGEST_LENGTH];
MD5_CTX c3;
unsigned char buffer3[MD5_DIGEST_LENGTH];
int i;
MD5_Init(&c);
MD5_Update(&c, "secret", 6);
MD5_Update(&c, "data", 4);
printf("A: %08x\n", c.A);
printf("B: %08x\n", c.B);
printf("C: %08x\n", c.C);
printf("D: %08x\n", c.D);
MD5_Final(buffer, &c);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer[i]);
printf("\n");
printf("A: %08x\n", c.A);
printf("B: %08x\n", c.B);
printf("C: %08x\n", c.C);
printf("D: %08x\n", c.D);
MD5_Init(&c2);
MD5_Update(&c2, "secret", 6);
MD5_Update(&c2, "data"
"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00"
"\x50\x00\x00\x00\x00\x00\x00\x00"
"append", 64);
MD5_Final(buffer2, &c2);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer2[i]);
printf("\n");
MD5_Init(&c3);
MD5_Update(&c3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 64);
c3.A = htonl(0x6036708e); /* <-- This is the hash we already had */
c3.B = htonl(0xba0d11f6);
c3.C = htonl(0xef52ad44);
c3.D = htonl(0xe8b74d5b);
MD5_Update(&c3, "append", 6); /* This is the appended data. */
MD5_Final(buffer3, &c3);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer3[i]);
printf("\n");
return 0;
}