【问题标题】:Optimization-stable constant-time array comparisons优化稳定的恒定时间数组比较
【发布时间】:2014-10-11 23:37:00
【问题描述】:

(注意:“恒定时间”是指当其中一个输入固定时,机器周期数是恒定的,而不是 O(1)。这是该术语在密码学上下文中的标准含义。)

比较一个固定值和一个相同大小的未知值以保证不会通过时序泄露有关固定值的信息的最常见方法是使用 XOR 循环:

bool compare(const char* fixed, const char* unknown, size_t n)
{
    char c = 0;
    for (size_t i=0; i<n; ++i)
        c |= fixed[i] ^ unknown[i];
    return (c == 0);
}

GCC 4.6.3 和 CLANG 3.0 不会在 AMD64 上短路这个循环,即使在 -O3 时也是如此(我检查了生成的机器代码)。但是,我不知道 C 标准中有什么会阻止一些聪明的编译器识别如果c 永远非零,那么函数只能返回false

如果您愿意接受较大的性能损失和概率比较而不是确定性比较,那么实现恒定时间比较的更偏执的方法是计算两个输入的加密哈希值并比较这些哈希值;如何比较散列并不重要,因为除非攻击者可以计算散列的原像,否则它无法对未知值进行连续逼近。很难想象一个编译器足够聪明来优化它,但我不能保证它不会发生。更偏执的方法是使用带有特定于实例的密钥而不是简单的哈希的 HMAC,尽管我仍然可以想象一个神奇的智能优化器,它认识到无论使用什么密钥,它正在编译的算法只是一种缓慢的方式进行字符串比较并进行相应的优化。通过添加额外的复杂层、对共享库的调用等,我可以使我的比较难以优化,但我仍然不能保证没有符合标准的编译器可以缩短我的比较并让我容易受到定时攻击。

有没有什么方法可以实现高效、确定性、恒定时间的比较,保证始终符合 C 标准?如果没有,是否有任何流行的 C(或 C++)编译器或库提供这种方法?请引用您的来源。

【问题讨论】:

  • “恒定时间”是指 O(1),还是“总是花费相同的时间来运行,无论输入如何,因此适合加密使用? "
  • OP 很清楚的意思是后者。
  • @templatetypedef:我的意思是当输入之一固定时,比较总是在相同数量的机器周期内运行。换句话说,没有时序侧通道可用于计算固定输入的值。

标签: c++ c optimization comparison standards


【解决方案1】:

“as-if”规则要求access to volatile objects be performed as written,所以我认为以下可能有效:

bool compare(const char* p1, const char* p2, size_t n)
{
    volatile char c = 0;
    for (size_t i=0; i<n; ++i)
        c |= p1[i] ^ p2[i];
    return (c == 0);
}

即使编译器可以静态确定两个输入数组是否不同,它仍然必须为c 的所有中间存储生成代码。因此生成的代码应该始终运行与n 成比例的时间。


版本 2,响应 AlexD 的帮助 cmets:

bool compare(volatile const char* p1, volatile const char* p2, size_t n)
{
    volatile char c = 0;
    for (size_t i=0; i<n; ++i)
        c |= p1[i] ^ p2[i];
    return (c == 0);
}

即使编译器可以静态确定函数的返回值,它仍然必须发出代码以从头到尾完整读取两个输入数组,并在这些加载之间写入c。优化仍然可以消除计算(XOR 和 OR),但内存行为将成为更明显的时序特征。在功率方面,攻击者可能仍然能够分辨出区别。

如果我们想消除 return 语句中依赖数据的分支,我们可以使用类似于 Go 的 ConstantTimeByteEq


请注意,the 'as-if' rule 的相关位已从 C++98/03 更改为 C++11:

1) 在每个序列点,所有 volatile 对象的值都是 稳定(之前的评估已完成,新的评估未完成 开始)(直到 C++11)

1) 对 volatile 对象的访问(读取和写入)严格发生 根据它们出现的表达式的语义。在 特别是,它们没有重新排序。 (C++11 起)

【讨论】:

  • 在您的情况下,如果编译器静态确定数组相同,则不需要为任何比较生成代码,对吧?只需将0 写入c n 次即可。
  • 您正在复制 OP 使用 n 作为索引的错误...您应该指出并修复它。 return 语句中也缺少分号(和不必要的括号)。
  • 好收获。我已经修复了您指出的正确性错误,但保留了 OP 中的风格选择。
  • @AlexD:两个输入可以被静态证明相同的情况在实践中不是问题,因为为了进行定时攻击,其中一个输入必须由攻击者。但这仍然是需要指出的一个很好的细节。
  • 更新:Robert Seacord(C 标准委员会的成员)告诉我这个答案是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-18
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 2014-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多