一、长度延展攻击
我们先来看看什么是长度延展,这样有利于你理解长度延展攻击
假设现在有两段数据 S 和 M,以及一个单向散列函数 h,我们把这两段数据合并起来,再计算合并后的散列值,这就是单向散列函数的长度延展
于是,问题来了,是 S 放在前面再计算 h(SM),还是 M 放在前面再计算 h(MS)?既然,我们说散列值是无法预测的,那么,数据的先后排列顺序还有意义吗?
如果 S 和 M 都是公开信息,顺序并不重要,可如果 S 是机密信息,M 是公开信息,那么,这两段数据的先后排列顺序就至关重要了,因为如果机密信息在前,就会有“长度延展攻击”的风险
弄清楚了长度延展,长度延展攻击就很好理解了,我们可以利用已知数据的散列值,计算出原数据外加一段延展数据后的散列值,也就是说,如果我们知道了 h(SM),我们就可以计算 h(SMN),其中,N 就是延展数据
如果 S 和 M 都是公开信息,即便计算出数据扩展后的散列值也没什么要紧的,但如果 S 是机密数据,比如说,当给出公开信息 M 后,只有知道机密数据的持有者才能计算 SM 的散列值,通过验证 SM 的散列值,就能得知公开信息 M 到底有没有被别人篡改过
比如下面的这段数据:
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 之间的是数据块补齐数据,并增添删除权限,而且又重新计算、替换了签名
其中,签名的计算需要用到机密数据,但攻击者并不知道机密数据,那攻击者又该如何伪造呢?要解决这个疑问,我们要先了解单向散列函数的构造,一个典型的单向散列函数,应该由四个部分组成:数据分组、链接模式、压缩函数和终结函数
- 压缩函数是单向函数,负责算法的单向性要求
- 终结函数不是单向函数,负责整理压缩函数的输出,形成散列值
- 链接模式,负责把下一段分组数据和上一个压缩函数的输出结果结合起来,确保算法的雪崩效应能够延续
值得一提的是,在 MD5,SHA-1,SHA-256 和 SHA-512 的算法设计中,终结函数就是把压缩函数的输出排列成一个字节串,知道了散列值,我们也就知道了最后一个压缩函数的输出结果,这里就是出现安全漏洞的地方
我们把原来的散列值作为压缩函数的一个输入,再按照数据补齐规范,补齐原来数据到数据分组的整数倍,然后加入新的数据,我们就可以计算原数据和扩展数据的散列值了,新的散列值计算,不需要知道机密数据,但整个计算过程,又的确使用了机密数据,但如果我们把公开信息 M 放在前面,机密信息 S 放在后面,长度延展攻击就不起作用了
二、如何有效避免长度延展攻击
一个单向散列函数,只要使用了类似上述的压缩函数和链接模式,都是“长度延展攻击”的可疑对象,其中,对于下列算法,长度延展攻击是完全有效的
- MD2
- MD5
- SHA-0
- SHA-1
- SHA-256
- SHA-512
对于下列算法,长度延展攻击虽然不是完全有效,但算法的安全级别显著降低了
- SHA-224
- SHA-384
对于下列算法,长度延展攻击完全没有效果
- SHA-512/224
- SHA-512/256
- SHA-3
- HMAC
三、MD5 长度延展攻击示例
#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];
MD5_CTX c4;
unsigned char buffer4[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);
printf("A: %08x\n", c.A);
printf("B: %08x\n", c.B);
printf("C: %08x\n", c.C);
printf("D: %08x\n", c.D);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer[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");
MD5_Init(&c4);
/* 64 字节任意数据 */
MD5_Update(&c4, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", 64);
c4.A = htonl(0x6036708e); /* <-- This is the hash we already had */
c4.B = htonl(0xba0d11f6);
c4.C = htonl(0xef52ad44);
c4.D = htonl(0xe8b74d5b);
MD5_Update(&c4, "append", 6); /* This is the appended data. */
MD5_Final(buffer4, &c4);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer4[i]);
printf("\n");
/* 验证 */
MD5_Init(&c2);
MD5_Update(&c2, "secret", 6);
MD5_Update(&c2, "data"
/* secret 6 字节 + data 4 字节 + n 字节(\x80\x00\x00...) + 8 字节 = 64 字节(64 整数倍字节)*/
"\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"
/* 8 字节填充数据大小(位),secertdata 10 字节,80 位 */
"\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");
return 0;
}
A: 67452301
B: efcdab89
C: 98badcfe
D: 10325476
A: 8e703660
B: f6110dba
C: 44ad52ef
D: 5b4db7e8
6036708eba0d11f6ef52ad44e8b74d5b
6ee582a1669ce442f3719c47430dadee
6ee582a1669ce442f3719c47430dadee
6ee582a1669ce442f3719c47430dadee
#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];
MD5_CTX c4;
unsigned char buffer4[MD5_DIGEST_LENGTH];
int i;
MD5_Init(&c);
MD5_Update(&c, "secret", 6);
MD5_Update(&c, "data:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:", 59);
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);
printf("A: %08x\n", c.A);
printf("B: %08x\n", c.B);
printf("C: %08x\n", c.C);
printf("D: %08x\n", c.D);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer[i]);
printf("\n");
MD5_Init(&c3);
MD5_Update(&c3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 128);
c3.A = htonl(0x6ba7c878); /* <-- This is the hash we already had */
c3.B = htonl(0xefb87921);
c3.C = htonl(0xec4eb9be);
c3.D = htonl(0x55d60699);
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");
MD5_Init(&c4);
MD5_Update(&c4, "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", 128);
c4.A = htonl(0x6ba7c878); /* <-- This is the hash we already had */
c4.B = htonl(0xefb87921);
c4.C = htonl(0xec4eb9be);
c4.D = htonl(0x55d60699);
MD5_Update(&c4, "append", 6); /* This is the appended data. */
MD5_Final(buffer4, &c4);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer4[i]);
printf("\n");
MD5_Init(&c2);
MD5_Update(&c2, "secret", 6);
MD5_Update(&c2, "data:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:"
/* secret 6 字节 + data... 59 字节 + n 字节(\x80\x00\x00...) + 8 字节 = 128 字节(64 整数倍字节) */
"\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\x00\x00\x00\x00\x00\x00\x00\x00\x00"
/* 8 字节填充数据大小(位),secertdata... 65 字节,520 位 */
"\x08\x02\x00\x00\x00\x00\x00\x00"
"append", 128);
MD5_Final(buffer2, &c2);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
printf("%02x", buffer2[i]);
printf("\n");
return 0;
}
A: 49235596
B: f5d7f7b8
C: 026afb5e
D: 149c540e
A: 78c8a76b
B: 2179b8ef
C: beb94eec
D: 9906d655
6ba7c878efb87921ec4eb9be55d60699
c1cd3e474d44831ba34e3633c2ddd379
c1cd3e474d44831ba34e3633c2ddd379
c1cd3e474d44831ba34e3633c2ddd379