免责声明:我很清楚实施自己的加密是一个非常糟糕的主意。这是硕士论文的一部分,代码不会在实践中使用。
作为更大的加密算法的一部分,我需要对一个恒定长度的数组(小的,精确的24)进行排序,而不会泄漏有关该数组内容的任何信息。据我所知(如果这些不足以防止时间和缓存攻击,请纠正我),这意味着:
是否存在任何此类实施?如果没有,这种类型的编程是否有任何好的资源?
老实说,我甚至在更容易的子问题上苦苦挣扎,即找到数组的最小值。
double arr[24]; // some input
double min = DBL_MAX;
int i;
for (i = 0; i < 24; ++i) {
if (arr[i] < min) {
min = arr[i];
}
}
添加带有虚拟分配的else
是否足以使其具有时间安全性?如果是这样,我如何确保编译器(在我的情况下是GCC)不会撤消我的辛勤工作?这会容易受到缓存攻击吗?
答案 0 :(得分:3)
使用分拣网络,一系列比较和交换。
交换调用不得依赖于比较。无论比较结果如何,都必须以执行相同数量指令的方式实现。
像这样:
void swap( int* a , int* b , bool c )
{
const int min = c ? b : a;
const int max = c ? a : b;
*a = min;
*b = max;
}
swap( &array[0] , &array[1] , array[0] > array[1] );
然后找到分拣网络并使用交换。这是一个为您执行此操作的生成器:http://pages.ripco.net/~jgamble/nw.html
4个元素的示例,数字是数组索引,由上面的链接生成:
SWAP(0, 1);
SWAP(2, 3);
SWAP(0, 2);
SWAP(1, 3);
SWAP(1, 2);
答案 1 :(得分:2)
这是一种非常愚蠢的冒泡排序,它实际上有效,并且根据输入数据不会分支或更改内存访问行为。不确定这是否可以插入另一种排序算法,他们需要将它们与掉期分开,但也许它可能,现在正在进行。
#include <stdint.h>
static void
cmp_and_swap(uint32_t *ap, uint32_t *bp)
{
uint32_t a = *ap;
uint32_t b = *bp;
int64_t c = (int64_t)a - (int64_t)b;
uint32_t sign = ((uint64_t)c >> 63);
uint32_t min = a * sign + b * (sign ^ 1);
uint32_t max = b * sign + a * (sign ^ 1);
*ap = min;
*bp = max;
}
void
timing_sort(uint32_t *arr, int n)
{
int i, j;
for (i = n - 1; i >= 0; i--) {
for (j = 0; j < i; j++) {
cmp_and_swap(&arr[j], &arr[j + 1]);
}
}
}
cmp_and_swap
函数编译为(Apple LLVM版本7.3.0(clang-703.0.29),使用-O3编译):
_cmp_and_swap:
00000001000009e0 pushq %rbp
00000001000009e1 movq %rsp, %rbp
00000001000009e4 movl (%rdi), %r8d
00000001000009e7 movl (%rsi), %r9d
00000001000009ea movq %r8, %rdx
00000001000009ed subq %r9, %rdx
00000001000009f0 shrq $0x3f, %rdx
00000001000009f4 movl %edx, %r10d
00000001000009f7 negl %r10d
00000001000009fa orl $-0x2, %edx
00000001000009fd incl %edx
00000001000009ff movl %r9d, %ecx
0000000100000a02 andl %edx, %ecx
0000000100000a04 andl %r8d, %edx
0000000100000a07 movl %r8d, %eax
0000000100000a0a andl %r10d, %eax
0000000100000a0d addl %eax, %ecx
0000000100000a0f andl %r9d, %r10d
0000000100000a12 addl %r10d, %edx
0000000100000a15 movl %ecx, (%rdi)
0000000100000a17 movl %edx, (%rsi)
0000000100000a19 popq %rbp
0000000100000a1a retq
0000000100000a1b nopl (%rax,%rax)
只有内存访问才能读取和写入数组,没有分支。编译器确实弄清了乘法实际上做了什么,实际上非常聪明,但它并没有使用分支。
强制转换为int64_t是必要的。我很确定它可以写得更干净。
根据要求,这里是双打比较功能:
void
cmp_and_swap(double *ap, double *bp)
{
double a = *ap;
double b = *bp;
int sign = signbit(a - b);
double min = a * sign + b * (sign ^ 1);
double max = b * sign + a * (sign ^ 1);
*ap = min;
*bp = max;
}
编译代码是无分支的,根据输入数据不会改变内存访问模式。
答案 2 :(得分:0)
非常简单,时间常数(但也非常高效)排序
没有早期休息,(几乎)不变的时间,不依赖于源的部分排序。