定时攻击将如何在特定代码上发生而不会在另一个代码上发生(由于良好的编码习惯)?有人可以举一个例子吗?我无法根据代码的编写方式弄清楚定时攻击将如何发生。

评论

我在这里写了一个与此问题有关的答案:crypto.stackexchange.com/a/40285/12962,尽管Biv比我的要彻底得多。

如果您担心,可以随时拨打睡眠电话(rand()):)

@mcfedr并没有真正的帮助-在足够大的样本量的情况下,统计分析可以消除那里的差异,因为随机数分布相对均匀。如果您的libc的随机数分布不均匀,则潜在的攻击者可以使用特定选择的数据主动对其进行测试,以识别“随机数”的分布,然后在执行定时攻击时将其考虑在内。 TL; DR:即使想一会儿,也能让您对易受攻击的加密程序定好时机!

#1 楼

TL; DR位于底部。

定时攻击的一般概念如下:


秘密数据对软件的定时产生影响衡量时间安排
攻击者计算影响力$ ^ {-1} $以获取秘密数据

基本弱点:if( secret )


可利用基础对定时攻击敏感的代码如下所示:

if(secret)
{
  do_A();
}
else
{
  do_B();
}


想法是,计算A的时间与计算B的时间不同。因此,知道了这一区别,您可以计算出秘密。

让我们看一下更接近真实代码的以下代码。
这是RSA解密的核心操作:$ a ^ d \ bmod n $,具有密钥$ d $:如果密码的值为if,则将执行(exp[i] >> j) & 1。如果密码的值是1,则将不执行任何操作。因此,根据密码的不同,执行时间也会有所不同。

让我们对代码进行一些改进:

typedef unsigned long long uint64;
typedef uint32_t uint32;

/* This really wants to be done with long integers */
uint32 modexp(uint32 a, uint32 mod, const unsigned char exp[4]) {
  int i,j;
  uint32 r = 1;

  for(i=3;i>=0;i--) {
    for(j=7;j>=0;j--) {
      r = ((uint64)r*r) % mod;
      if((exp[i] >> j) & 1)
        r = ((uint64)a*r) % mod;
    }
  }
  return r;
}


在这段代码,您可能会认为,因为我们在依赖秘密r = ((uint64)a*r) % mod0语句的两个分支上执行相同的操作:


1赋值。
1乘法
1模运算

执行任何一个分支的时间都将是相同的,对吗?

好...否。因为... if是一个无效变量,并且编译器当然会注意到它,并将优化代码。 “此变量无用,将其清除!”。因此,编译后的代码将与您之前遇到的计时漏洞之前的代码相同。 />

现代CPU具有分支预测。
和指令缓存。

因此,我们需要摆脱这种分支弱点。

删除分支

如何删除类似这样的分支:

typedef unsigned long long uint64;
typedef uint32_t uint32;

/* This really wants to be done with long integers */
uint32 modexp(uint32 a, uint32 mod, const unsigned char exp[4]) {
  int i,j;
  uint32 r = 1, t;

  for(i=3;i>=0;i--) {
    for(j=7;j>=0;j--) {
      r = ((uint64)r*r) % mod;
      if((exp[i] >> j) & 1)
        r = ((uint64)a*r) % mod;
      else
        t = ((uint64)a*r) % mod;
    }
  }
  return r;
}


我们将其替换为以下内容:

if (s)
  r = do_A()
else
  r = do_B()


由于我们需要快速的代码,因此可以将(exp[i] >> j) & 1扩展为全一个/全-零掩码,并使用XOR代替加法,并使用AND代替乘法。

所以回到我们的平方乘:其中t(作为条件移动)是同名的汇编程序指令(不使用任何预测)或定义为以下内容:

r = s * do_A() + (1 - s) * do_B()


另一个缺点: s


想法如下,处理器的缓存包含表。并且由于可以在同一时间范围内访问缓存中的所有内容,因此您的秘密是安全的。

uint32 modexp(uint32 a, uint32 mod, const unsigned char exp[4]) {
  int i,j;
  uint32 r = 1,t;

  for(i=3;i>=0;i--) {
    for(j=7;j>=0;j--) {
      r = ((uint64)r*r) % mod;
      t = ((uint64)a*r) % mod;
      cmov(&r, &t, (exp[i] >> j) & 1);
    }
  }
  return r;
}


这是正确的……如果仅运行此代码。但是,您总是在CPU上同时运行其他代码,因此表的某些部分如下所示:

/* decision bit b has to be either 0 or 1 */
void cmov(uint32 *r, const uint32 *a, uint32 b)
{
  uint32 t;
  b = -b; /* Now b is either 0 or 0xffffffff */
  t = (*r ^ *a) & b;
  *r ^= t;
}


我们假设攻击者具有控制缓存的哪一部分损坏。因此,如果cmovtable[secret],那么您将立即从缓存中得到响应。但是,如果secret0x0001,则缓存无效。因此,CPU将必须从堆栈/存储器中重新加载该值。这会花费更长的时间,因为我们存在时间上的差异...我们可能会出现时间上的攻击。<​​br />
反制措施非常复杂,我将不再赘述。

参考资料

答案的大部分来自这张幻灯片:彼得·施瓦贝(Peter Schwabe)在2016年Crypto Summer School-克罗地亚的定时攻击和对策。

一些有趣的读物:

Osvik,Shamir,Tromer,2006:
缓存攻击和对策:以AES为例。
http://eprint.iacr.org/2005/271/

AlFardan,Paterson,2013年:幸运的十三:打破TLS和DTLS记录协议。
http://www.isg .rhul.ac.uk / tls / Lucky13.html

Yarom,福克纳,2014年:冲洗+重新加载:高分辨率,低噪声,L3缓存侧通道攻击。
http: //eprint.iacr.org/2013/448/

Benger,van de Pol,Smart,Yarom,2014年:“哦,啊……只是一点点”:少量的旁通道可以走很长的路。
http://eprint.iacr.org/2014/161/

Bernstein,2005年:对AES的缓存定时攻击。
http:/ /cr.yp.to/papers.html#cachetiming

Brickell,2011年:提高平台安全性的技术。
http://www.chesworkshop.org/ches2011/presentations/Invited% 201 / CHES2011_Invited_1.pdf

伯恩斯坦,施瓦贝,2013年:警告语。
https://cryptojedi.org/peter/data/chesrump-20130822.pdf
https ://cryptojedi.org/peter/data/cacheline.tar.bz2

Yarom,Genkin,Heninger,2016:CacheBleed:A Timing Atta参见OpenSSL恒定时间RSA
https://ssrg.nicta.com.au/projects/TS/cachebleed/

汉堡,2009年:使用矢量置换指令加速AES。 > http://mikehamburg.com/papers/vector_aes/vector_aes.pdf

Biham,1997年:“一种快速的新DES在软件中的实现。”
http://www.cs。 technion.ac.il/users/wwwb/cgi-bin/tr-info.cgi?1997/CS/CS0891

TL; DR

如果您阅读并跳过了整个答案,那么就意味着您肯定不想编写加密代码。

如果您确实阅读了整个答案,那么您可能会理解为什么用以下方法编写加密代码是个坏主意给出了多种解决方法。

评论


$ \ begingroup $
dat厚脸皮的小tl; dr剧情在最后!
$ \ endgroup $
– Tasos Papastylianou
16-11-21在14:31

$ \ begingroup $
每次您有分支机构(人们往往不知道自己有多少分支机构),都会有一个跳跃,而且几乎总是有条件的。跳转足够糟糕,但是条件分支是致命的。在过去的CPU中,它们只会导致流水线冲洗。对于现代的CPU,如果CPU错误地预测了分支是否会失败,则可能会出现这种情况。无论哪种方式,实际上任何分支都会泄漏信息,即使其他所有信息都是固定时间的直到组装。并且不要因为缺少if,for,while和朋友而被愚弄;是的,?:可能是分支!
$ \ endgroup $
–用户
16-11-21在17:25



#2 楼

以下示例在现实生活中难以利用,并且无法在网络上使用,但是它很容易理解和推断。

考虑服务器上检查MAC正确性的一段代码。

int compare_mac(unsigned char *mac1, unsigned char *mac2, size_t n)
{
    for (; n--; mac1++, mac2++) {
        if (*mac1 != *mac2) {
            return *mac1 - *mac2;
    }
    return 0;
}


如果MAC不匹配,则该功能会提前退出。换句话说,第一个字节不同的两个MAC将几乎立即返回,而仅最后一个字节不同的MAC将需要更多时间进行比较。

现在假设恶意用户试图发送一个不知道密钥的消息。假设他也可以非常准确地计时,足以区分几个指示。他可以简单地发送随机MAC并给响应计时,并找出MAC的每个字节,每个字节最多256个猜测。他首先猜测第一个字节,然后猜测第二个字节,等等。换句话说,他将最多需要对服务器进行$ 256n $的调用才能伪造MAC。

如果上面的编码没有早日返回,并且对于长度为$ n $的所有输入都是恒定时间,那么攻击者将需要对服务器的$ 256 ^ n $调用来伪造MAC。

显然,这是一个巨大的差异。

实际上,您可能无法区分一些指令,尤其是在网络上。但是,其他时序泄漏可能会更大,使用一些基本的统计分析可以使您走得更远。

评论


$ \ begingroup $
IIRC Windows 95的SMB文件共享支持中存在一个错误,该错误允许针对共享密码的攻击有所不同,但基于密码长度而不是时间。就像是这样,如果您发送了$ n $字符密码,则仅检查共享密码的前$ n $个字符,而不管共享密码的长度如何,并将成功/失败结果返回给客户端基于这些$ n $字符的正确性。哎呀。 :-)
$ \ endgroup $
–用户
16-11-21在17:31

$ \ begingroup $
FWIW,当我使用简单的字符串方程式(if(“ something” == $ _POST [“ field”]))在PHP中完成MAC检查时,我无法重现此攻击。即使进行5000次尝试并进行最快的尝试(以及达到此最快时间的频率),或者最快的10%,或者最快的30%减去最快的10%...在按以下顺序尝试时,没有一个给我可靠的答案两个或三个字符,例如当机密为aaaa时提交aa与bb。有时aa(正确的a)更快,有时bb更快。
$ \ endgroup $
–吕克
17-2-24在19:19



$ \ begingroup $
@Luc这并不奇怪,它更多是一个理论上的例子。区分aa和bb的时序本质上是试图对少数几条指令进行计时,这些指令将以几纳秒的量级执行。另外,也许PHP字符串比较功能会首先检查长度是否相等?
$ \ endgroup $
– bkjvbx
17年2月25日在14:37

$ \ begingroup $
@bkjvbx也许,我不知道。编写POC比浏览我认为的源代码要快。我之所以这样做,是因为在检查PHP中的密码时,我总是尝试进行恒定时间比较,但是我想知道它的脆弱性以及它是否足够健壮以至于可以在Internet上工作(考虑到连接)。好吧,它根本不容易受到攻击(长度检查),甚至在本地主机上也存在太多抖动。
$ \ endgroup $
–吕克
17年2月25日在15:33

$ \ begingroup $
此攻击永远不应应用于密码,因为密码应由功能强大且速度较慢的密码哈希函数服务器端进行哈希。这意味着,即使攻击者知道密码的哈希值,他也将需要执行前映像攻击才能找到匹配的密码。您在密码验证中做的错误非常严重,或者(希望)误解了这种攻击的工作方式:)
$ \ endgroup $
– bkjvbx
17-2-25在17:01