这会根据a 和b 之间是否存在差异来设置diff。
它通过始终遍历a 和b 两者中较短的一个来避免定时攻击,无论是否比这更早出现不匹配。
diff |= (uint)(a[i] ^ (uint)b[i]) 将a 的一个字节与b 的对应字节进行异或。如果两个字节相同,则为 0,如果不同,则为非零。然后ors 与diff。
因此,如果在迭代中发现输入之间存在差异,diff 将在迭代中设置为非零。一旦diff 在循环的任何迭代中被赋予非零值,它将通过进一步的迭代保持非零值。
因此,如果在a 和b 的对应字节之间发现任何差异,则diff 中的最终结果将非零,并且仅当a 的所有字节(和长度)和b 相等。
但是,与典型的比较不同,这将始终执行循环,直到两个输入中较短的一个字节中的所有字节都与另一个输入中的字节进行了比较。典型的比较会提前结束,一旦发现不匹配,循环就会中断:
bool equal(byte a[], byte b[]) {
if (a.length() != b.length())
return false;
for (int i=0; i<a.length(); i++)
if (a[i] != b[i])
return false;
return true;
}
这样,根据返回 false 所花费的时间,我们可以了解(至少是近似值)在 a 和 b 之间匹配的字节数。假设长度的初始测试需要 10 ns,循环的每次迭代需要另外 10 ns。基于此,如果它在 50 ns 内返回 false,我们可以快速猜测我们的长度是正确的,并且 a 和 b 的前四个字节匹配。
即使不知道确切的时间量,我们仍然可以使用时间差异来确定正确的字符串。我们从长度为 1 的字符串开始,一次增加一个字节,直到我们看到返回 false 的时间增加。然后我们遍历第一个字节中的所有可能值,直到我们看到另一个增加,这表明它已经执行了循环的另一个迭代。对连续的字节继续使用相同的方法,直到所有字节都匹配并且我们得到 true 的返回。
原文仍然存在少许位的定时攻击——虽然我们不能轻易根据定时确定正确字符串的内容,但我们至少可以找到字符串长度基于时间。由于它只比较两个字符串中较短的一个,我们可以从长度为 1 的字符串开始,然后是 2,然后是 3,依此类推,直到时间稳定。只要时间在增加,我们建议的字符串就会比正确的字符串短。当我们给它更长的字符串,但时间保持不变时,我们知道我们的字符串比正确的字符串长。正确的字符串长度将是需要最长测试时间的最短字符串。
这是否有用取决于情况,但无论如何,它显然会泄露一些信息。为了真正实现最大的安全性,我们可能希望将随机垃圾附加到真实字符串的末尾,以使其成为用户输入的长度,因此时间与输入的长度成正比,无论它是否更短、相等到,或比正确的字符串长。