哈希长度延展攻击

一、长度延展攻击

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

假设现在有两段数据 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值