我有一段时间试图提出一个不违反 C/C++ 标准的恒定时间轮换。
问题是边缘/角落的情况,其中运算在算法中被调用并且这些算法无法更改。例如,以下来自Crypto++并在GCC ubsan(即)下执行测试工具g++ fsanitize=undefined
:
$ ./cryptest.exe v | grep runtime
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:625:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
和代码misc.h:637
:
template <class T> inline T rotlMod(T x, unsigned int y)
{
y %= sizeof(T)*8;
return T((x<<y) | (x>>(sizeof(T)*8-y)));
}
Intel 的 ICC 尤其无情,它把整个函数调用都去掉了,没有y %= sizeof(T)*8
. 几年前我们修复了这个问题,但由于缺乏恒定的时间解决方案,将其他勘误保留在原地。
剩下一个痛点。什么时候y = 0
,我得到一个条件 where 32 - y = 32
,它设置了未定义的行为。如果我添加检查if(y == 0) ...
,则代码无法满足恒定时间要求。
我查看了许多其他实现,从 Linux 内核到其他加密库。它们都包含相同的未定义行为,因此它似乎是一个死胡同。
如何以最少的指令在几乎恒定的时间内执行旋转?
编辑:通过接近恒定的时间,我的意思是避免分支,所以总是执行相同的指令。我不担心 CPU 微码计时。虽然分支预测在 x86/x64 上可能很好,但在其他平台上可能表现不佳,例如嵌入式。
如果GCC或Clang提供了在接近恒定的时间内执行旋转的内在函数,则不需要这些技巧。我什至会满足于“执行轮换”,因为他们甚至没有。