从Redis源码 acl.c->time_independent_strcmp() 实现谈到 定时攻击

问题引出

在Redis阅读Redis源码的时候,发现acl.c中有个方法time_independent_strcmp。它的作用就是用来对比两个字符串是否相等,如果相等,那么就返回0,如果不相等,那么就返回非0值。
是不是很熟悉,没错!这跟C语言中的strcmp功能是一样的。那么为什么Redis的作者需要自己去实现一个呢?

我们来看一下这个函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int time_independent_strcmp(char *a, char *b) {
char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
/* The above two strlen perform len(a) + len(b) operations where either
* a or b are fixed (our password) length, and the difference is only
* relative to the length of the user provided string, so no information
* leak is possible in the following two lines of code. */
unsigned int alen = strlen(a);
unsigned int blen = strlen(b);
unsigned int j;
int diff = 0;

/* We can't compare strings longer than our static buffers.
* Note that this will never pass the first test in practical circumstances
* so there is no info leak. */
if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;

memset(bufa,0,sizeof(bufa)); /* Constant time. */
memset(bufb,0,sizeof(bufb)); /* Constant time. */
/* Again the time of the following two copies is proportional to
* len(a) + len(b) so no info is leaked. */
memcpy(bufa,a,alen);
memcpy(bufb,b,blen);

/* Always compare all the chars in the two buffers without
* conditional expressions. */
for (j = 0; j < sizeof(bufa); j++) {
diff |= (bufa[j] ^ bufb[j]);
}
/* Length must be equal as well. */
diff |= alen ^ blen;
return diff; /* If zero strings are the same. */
}

可以看到,这个方法会先对比两个字符串的每一位,然后再判断两个字符串的长度是否相等。
看到这里,你是不是会想这样实现岂不是每次都会去对比所有字符,效率比strcmp还低!意义在哪呢?没错,我当时也是这么想的。于是去搜索了issue,于是有了意外的收获:https://github.com/redis/redis/issues/560
Redis作者原来实现也的确使用的是strcmp,在这个issue中,有人提出了《定时攻击》这个安全问题,于是Redis作者就这个问题进行了修复,也就是time_independent_strcmp方法的由来。

定时攻击

简单来说,就是通过多次的密码攻击形式进行破解攻击,并根据目标服务返回的时间来确定当前字符是否正确。
strcmp方法中,本质就是将两个字符串从头开始逐一比较,发现不同就立刻停止返回。假如输入的两个串开头有一部分相同,那么判断字符所耗费的时间就要变长一点。这好像就在告诉别人你输入的密码错误,但是前两个字符是正确的

解决这种攻击一般有几种方法:

  • 随机休眠时间
  • 估算执行时间,固定返回时间
  • 固定执行流程,返回时间不受具体字符影响。

在Redis这种高性能服务中,随机休眠以及将命令执行时间固定这两种方式会严重的影响执行效率。
所以Redis的作者通过在密码比较时,无论正确与否都正常执行下去,尽可能的返回相同时间,以避免这种攻击来破解密码。

https://github.com/redis/redis/issues/560
密码学中恒定时间设计(译文)