考虑下边的代码
int memcmp(const void *s1, const void *s2, size_t n) {
if (n != 0) {
const unsigned char *p1 = s1, *p2 = s2;
do {
if (*p1++ != *p2++)
return ((*--p1) - (*--p2));
} while (--n != 0);
}
return (0);
}
如果这是进行密码验证的程序,那么黑客可以根据返回时间的不同,判断出输入的密码是在哪一位开始出错的。
因此在密码领域,引出了常量时间编程。
取绝对值,直觉上,正数是其本身,负数是自身减1再取反。
是需要if进行判断的。
这样程序执行的时间,就不是一个常量了。
引出常量时间的编码:
#include
int32_t abs(int32_t x) {
int32_t mask = (x >> 31);
return (x + mask) ^ mask;
}
注:
C语言定义,无符号数右移,是逻辑右移。
有符号数右移,是算术右移。
左移的话,有没有符号的数,都一样。
当有符号数x是正数时,
mask = 0,(x + 0)^ 0 = x
当有符号数x是负数时,
mask = 0xFFFF_FFFF,即是 -1
而且和 0xFFFF_FFFF 进行异或 *** 作,就是在取反。
(x - 1)^ 0xFFFF_FFFF 就是进行了减1再取反的 *** 作。
注:
C语言取绝对值当中,有一个未定义的情况。
abs(INT_MIN) 的值是未定义的,由编译器厂商进行安排。
INT_MIN = 0x8000_0000 = -2147483648
这个值带入到上述代码,得到的值还是 -2147483648,这是错的。
取绝对值的代码再来一个:
int32_t abs(int32_t x) {
int32_t mask = (x >> 31);
return (x ^ mask) - mask;
}
正数就不说了,因为mask=0。
对于负数,乍一看,是先取反,再加一。
怎么又搞了一次取反加一啊,啥?这样再搞一次就取到负数对应的绝对值了???
证明如下:
x + x逆 = 0
x + x反 = 0xFFFF_FFFF
上述代码结果为 abs(x) = x反 + 1
两边同时加上x
x + abs(x) = x + x反 + 1 = 0xFFFF_FFFF + 1 = 0(最高位的1会溢出)
得到
当x是负数时,abs(x) = -x
这是成立的。
这个代码改为x86汇编时,将变成:
cdq
xor eax, edx
sub eax, edx
cdq是将eax的最高位填充到edx当中。
eax是正数时,edx是0。
负数时,edx填充全1.
然后运算 eax = eax ^ edx
只考虑负数情况,此时eax等于自身取反了。
再看 eax = eax - edx
edx是-1,此时eax又加了一个1
因此这个汇编代码,就是上边的C语言代码。
再来两个执行时间是常量的例子,自行思考。
取出较小的数
int32_t min(int32_t a, int32_t b) {
int32_t diff = (a - b);
return b + (diff & (diff >> 31));
}
如果a大于b,则x+=c
x += ((b - a) >> 31) & c;
完。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)