一个天真的实现是memcmp()
这样的(来自这个答案):
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()
实现和调用站点(即使它们位于不同的翻译单元中)。可以看到调用站点没有使用实际值,它只是将其与 null 进行比较。因此,它可以自由转换CRYPTO_memcmp()
,以便在找到第一个不匹配的字节对并且“安全”版本memcmp()
不再安全时立即返回。
如何memcmp()
实现使得符合标准的编译器不会将其转换为有助于定时攻击的版本?