memcmp()
的简单实现如下所示(来自this answer):
int memcmp_test(const char *cs_in, const char *ct_in, size_t n)
{
size_t i;
const unsigned char * cs = (const unsigned char*) cs_in;
const unsigned char * ct = (const unsigned char*) ct_in;
for (i = 0; i < n; i++, cs++, ct++)
{
if (*cs < *ct)
{
return -1;
}
else if (*cs > *ct)
{
return 1;
}
}
return 0;
}
字符串
在这里,一旦发现第一个不匹配的字节,块遍历就停止。这对加密应用程序来说可能没有好处,因为它使执行时间依赖于块的内容,这可能允许计时攻击。所以OpenSSL使用这个(从这里引用):
int CRYPTO_memcmp(const void *in_a, const void *in_b, size_t len)
{
size_t i;
const unsigned char *a = in_a;
const unsigned char *b = in_b;
unsigned char x = 0;
for (i = 0; i < len; i++)
x |= a[i] ^ b[i];
return x;
}
型
中间没有break
s或return
s,所以这段代码必须遍历整个块长度。至少这是意图。
下面是一个使用示例(从这里):
static int des_ede3_unwrap(EVP_CIPHER_CTX *ctx,
unsigned char *out, const unsigned char *in, size_t inl)
{
unsigned char icv[8], iv[8], sha1tmp[SHA_DIGEST_LENGTH];
//whatever, unrelated then...
if (!CRYPTO_memcmp(sha1tmp, icv, 8))
rv = inl - 16;
//whatever, unrelated
}
型
现在有了链接时代码生成(Visual C++ LTCG)或链接时优化(gcc LTO),编译器能够看到CRYPTO_memcmp()
实现和调用站点(即使它们在不同的翻译单元中)。它可以看到调用站点没有使用实际值,所以它可以自由地转换CRYPTO_memcmp()
,这样一旦它发现第一个不匹配的字节对,memcmp()
的“安全”版本就不再安全了。
如何实现memcmp()
,使符合标准的编译器不会将其转换为有助于定时攻击的版本?
1条答案
按热度按时间tyu7yeag1#
有两种可能的解决方案。
第一个是绝对可移植的,符合标准- declare
x
volatile,它基本上告诉编译器它必须保留更新x
的顺序,因此它必须完全读取两个数据数组。(例如一次读取几个字节,然后按正确的顺序使用它们),但在这里没什么大不了的编译器将不得不发出与数据数组中的字节数成比例的读取次数。这种方法的问题是它会使代码变慢-我运行的一些基准测试显示,在特定的处理器和特定的工具集上,速度降低了大约50%。第二种可能的解决方案是将指针强制转换为
volatile unsigned char*
并通过它们进行访问字符串
这是一样快,但不完全符合标准(见this)。许多编译器处理这种转换作为一个提示,这些读取不应该被改变,但标准并没有真正作出任何保证,所以它是可能的,编译器打破这段代码的目的。因此,这种解决方案是不可移植的。