屏蔽是否有效阻止侧通道攻击?

我正在使用一些bigint公钥加密代码。 使用按位屏蔽以确保访问的计算时序和存储器地址与数据值无关是否安全?

这种技术是否容易受到基于指令时序,功率,RF发射或其他我不知道的事情的旁道攻击? (作为参考,我知道RSA盲法,EC Montgomery梯形图,缓存刷新等技术。)


简单代码示例(C / C ++):

uint a = (...), b = (...); if (a < b) a += b; 

现在翻译为使用恒定时间屏蔽:

 uint a = (...), b = (...); uint mask = -(uint)(a < b); a = ((a + b) & mask) | (a & ~mask); 

注意a < b是0或1,掩码是0x00000000或0xFFFFFFFF。


同样,对于高级操作(C ++):

 Integer x = (...); if (x.isFoo()) x.doBar(); 

以下是可接受的安全翻译吗?

 Integer x = (...); uint mask = -(uint)x.isFoo(); // Assume this is constant-time Integer y(x); // Copy constructor y.doBar(); // Assume this is constant-time x.replace(y, mask); // Assume this uses masking 

这种技术可能是安全的…如果我们假设采取恒定时间的操作确实如此,并且如果编译器没有改变代码来做其他事情。

特别是,让我们来看看你的第一个例子:

 uint a = (...), b = (...); uint mask = -(uint)(a < b); a = ((a + b) & mask) | (a & ~mask); 

我看到两种有些合理的方式可能无法在恒定的时间内运行:

  1. 比较a < b可能会也可能不会占用恒定时间,具体取决于编译器(和CPU)。 如果它被编译为简单的位操作,它可能是恒定时间; 如果它被编译为使用条件跳转,它可能不是。

  2. 在高优化级别,过于聪明的编译器可能会检测到正在发生的事情(例如,通过基于比较将代码分成两个路径,并在合并它们之前单独优化它们)并将其“优化”回非 - 我们试图避免的不变时间码。

    (当然,如果它认为效率更高,那么一个足够聪明的编译器也可以将天真的,看似非常规的时间代码优化为恒定时间操作!)

避免第一个问题的一种可能方法是用显式位操作替换比较,如:

 uint32_t a = (...), b = (...); uint32_t mask = -((a - b) >> 31); a = ((a + b) & mask) | (a & ~mask); 

但请注意,如果我们可以确定ab相差小于2 31 ,则这仅相当于您的原始代码。 如果不能保证,我们必须在减法之前将变量转换为更长的类型,例如:

 uint32_t mask = (uint32_t)(( (uint64_t)a - (uint64_t)b ) >> 32); 

所有这一切,即使这不是万无一失的,因为编译器仍然可以决定将此代码转换为非常规时间的代码。 (例如,32位CPU上的64位减法可能需要可变时间,具体取决于是否存在借位 - 这正是我们试图隐藏的内容,这里。)

通常, 确保不发生此类定时泄漏的唯一方法是:

  1. 手动检查生成的汇编代码(例如,查找您没有预料到的跳转指令),以及

  2. 实际上,对代码进行基准测试以validation它确实无论输入如何都要花费相同的时间来运行。

显然,您还需要为希望支持的编译器和目标平台的每个组合单独执行此操作。

在代码中使用掩码或其他技术可能很粗略,因为编译器会执行您经常不了解的各种优化。 您在原始post中提到的一些方法要好得多。

一般的经验法则是使用众所周知的加密库,因为它们应该加强对抗侧通道攻击。 如果失败,您通常可以转换信息,处理信息然后转换回结果。 这对于公钥密码术尤其有效,因为它通常是同态的。