随机睡眠可以防止定时攻击吗?

Posted

技术标签:

【中文标题】随机睡眠可以防止定时攻击吗?【英文标题】:Could a random sleep prevent timing attacks? 【发布时间】:2015-04-08 08:38:19 【问题描述】:

来自Wikipedia

在密码学中,定时攻击是一种边信道攻击,其中 攻击者试图通过分析时间来破坏密码系统 用于执行加密算法。

实际上,为了防止定时攻击,我使用了以下来自this answer的函数:

function timingSafeCompare($safe, $user) 
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    $safeLen = strlen($safe);
    $userLen = strlen($user);

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) 
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    

    // They are only identical strings if $result is exactly 0...
    return $result === 0;

但我在想是否可以使用随机睡眠来防止这种攻击

function timingSafeCompare($a,$b) 
    sleep(rand(0,100));
    if ($a === $b) 
        return true;
     else 
        return false;
    

或者也许增加睡眠的随机性

sleep(rand(1,10)+rand(1,10)+rand(1,10)+rand(1,10));

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

【问题讨论】:

只需使用 hash_equals() 并收工。 【参考方案1】:

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

两者都不是。它不会阻止定时攻击,也不会让它们变得更困难。

要了解原因,请查看docs for sleep。具体来说,第一个参数的含义:

暂停时间以秒为单位。

因此,您的应用需要 0.3 秒才能在不休眠的情况下做出响应。睡眠需要 0.3、1.3、2.3 等...

所以说真的,要得到我们关心的部分(时间差),我们只需要去掉整数部分:

$real_time = $time - floor($time);

但让我们更进一步。假设您使用usleep 随机睡眠。这要细得多。那是在微秒内休眠。

嗯,测量是在 15-50 纳米秒的范围内进行的。因此,睡眠的粒度仍然比正在进行的测量大约 100 倍。所以我们可以平均到一微秒:

$microseconds = $time * 1000000;
$real_microseconds = $microseconds - floor($microseconds);

并且仍然有有意义的数据。

您可以更进一步并使用time_nanosleep,它可以睡眠到纳秒级精度。

然后你就可以开始玩弄数字了。

但数据仍然存在。随机性的美妙之处在于您可以将其平均化:

$x = 15 + rand(1, 10000);

运行足够多的时间,你会得到一个漂亮的图表。您会知道大约有 10000 个不同的数字,因此您可以平均掉随机性并推断出“私人”15。

由于表现良好的随机性是无偏的,因此在足够大的样本上进行统计检测非常容易。

所以我要问的问题是:

既然您可以正确解决问题,为什么还要费心使用类似睡眠的技巧?

【讨论】:

我喜欢我是其他两个答案中引用的帖子的作者,他们的投票比我高:-P...(只是玩,这实际上是一种荣誉)跨度> 不错的博文。也许您可以使用某种计时攻击来破解问题并使其接受您的答案。 你确定他们“不会让他们变得更困难”吗?有吗?如果一个响应时间平均为 1 毫秒,并且您添加了大约 1 秒的随机睡眠,那么至少现在攻击者必须再等待一千倍(或使用更多线程),这使得攻击不会更难,但至少更长。如果我的 web 应用程序在 1 周内有疯狂的流量,那比凌晨 3 点的短暂流量更容易发现。 我不是统计学家,但是您上面的示例中的“15”不是总是根据外部因素(例如服务器负载)而不一致吗?因此,如果你有一些通常需要 12 到 18 微秒的操作,并且你引入了 0 到 6 微秒的睡眠,那么就不可能检测到它。另一种选择是让所有操作花费固定的时间,即睡眠实际花费的时间和任意时间长度的差异? 你认为this sleep-based hack 能防止定时攻击吗?【参考方案2】:

Anthony Ferrara 在他的博文It's All About Time 中回答了这个问题。我强烈推荐这篇文章。

很多人在听到定时攻击时会想:“好吧,我只是添加一个随机延迟!这样就可以了!”。还有it doesn't。

【讨论】:

很多漂亮的图表,我相信实际的讲座会解释它,但我个人看不出添加真正随机的睡眠如何不足以减轻定时攻击。 @Phil_1984_ 1) 粒度。大多数“随机”睡眠都在毫秒级,我们测量的差异在 10 纳秒级。 2) 地方性。硬件上的代理可以监视 CPU 负载并根据活动(在 VM 等中)告诉睡眠。 3)从统计上可以平均掉那些随机睡眠。 4)这是一个创可贴。它没有减轻,它没有堵塞潜在的伤口,它只是让你看不到它。这是默默无闻的。 啊,我只看了第二个链接。第一个链接解释了这一切,非常有趣,谢谢。你只需要做很多请求来平均随机性。负载(如您所说)和每个网络跃点也存在随机性,理论上可以以相同的方式平均化。对于 OPs 字符串比较问题,简单地先哈希然后比较结果有什么问题吗? @Phil_1984_ 仅仅散列是不够的(因为散列是可预测的)。您需要使用随机字符串(最好在 HMAC 中)进行散列:isecpartners.com/blog/2011/february/…(注意使用静态密钥,但我们中的许多人建议使用随机密钥)。 是的,如果您对正确的解决方案过敏,那么带有随机数的双 HMAC 确实会使定时攻击变得不切实际。【参考方案3】:

如果攻击者可观察到的唯一侧通道是响应时间,这对于单个请求来说是可以的。

但是,如果攻击者发出足够多的请求,则此随机延迟可能会平均化,如 @Scott's answer 引用 ircmaxell's blog post 中所述:

因此,如果我们需要运行 49,000 次测试来获得 15ns 的准确度 [没有随机延迟],那么我们可能需要 100,000 或 1,000,000 次测试才能获得相同的准确度和随机延迟。或者可能是 100,000,000。但是数据还在。

举个例子,让我们估计一个定时攻击需要多少请求才能获得一个有效的 160 位会话 ID,比如 php at 6 bits per character which gives a length of 27 characters。假设像linked answer 一样,一次只能对一个用户进行攻击(因为他们将用户存储在 cookie 中以进行查找)。

从博客文章中提取最好的情况,100,000,排列的数量将是100,000 * 2^6 * 27

平均而言,攻击者会在排列数的中间找到值。

这使得从定时攻击中发现会话 ID 所需的请求数为 86,400,000。这与没有您建议的时序保护的 42,336,000 个请求相比(假设精度为 15ns,如博客文章)。

在博文中,测试最长的长度 14 平均需要 0.01171 秒,这意味着 86,400,000 需要 1,011,744 秒,相当于 11 天 17 小时 2 分 24 秒。

随机睡眠可以防止定时攻击吗?

这取决于使用随机睡眠的上下文以及它所保护的字符串的位强度。如果它是用于链接问题中的上下文的“让我登录”功能,那么攻击者可能值得花费 11 天时间来使用定时攻击来暴力破解一个值。但是,这是假设条件完美(即,您的应用程序对每个测试的字符串位置的响应时间相当一致,并且没有重置或翻转 ID)。此外,来自攻击者的此类活动会产生大量噪音,并且很可能会通过 IDS 和 IPS 被发现。

它不能完全阻止它们,但它可以使攻击者更难以执行它们。使用hash-equals 之类的东西会更容易和更好,这会在字符串长度相等的情况下完全防止定时攻击。

您建议的代码

function timingSafeCompare($a,$b) 
    sleep(rand(0,100));
    if ($a === $b) 
        return true;
     else 
        return false;
    

请注意,PHP rand 函数不是加密安全:

注意 此函数不会生成加密安全值,并且不应用于加密目的。如果您需要加密安全值,请考虑改用 openssl_random_pseudo_bytes()

这意味着理论上攻击者可以预测rand 将生成什么,然后使用此信息来确定您的应用程序的响应时间延迟是否是由于随机睡眠造成的。

解决安全问题的最佳方法是假设攻击者知道您的源代码 - 攻击者唯一的秘密应该是密钥和密码之类的东西 - 假设他们知道所使用的算法和功能。如果您仍然可以说您的系统是安全的,即使攻击者确切地知道它是如何工作的,那么您将大部分时间在那里。像rand 这样的函数通常设置为当前时间,因此攻击者只需确保他们的系统时钟设置为与您的服务器相同,然后发出请求以验证他们的生成器是否与您的匹配。

因此,最好避免像 rand 这样不安全的随机函数,并将您的实现更改为使用不可预测的 openssl_random_pseudo_bytes

另外,根据 ircmaxell 的评论,sleep 不够精细,因为它只接受一个整数来表示秒数。如果您打算尝试这种方法,请查看 time_nanosleep 随机数纳秒。

这些指针应该有助于保护您的实现免受此类定时攻击。

【讨论】:

另外:sleep() 接受 seconds 作为参数。它的粒度太低,无法隐藏任何东西...... 在我看来,随机睡眠不是一个好的选择。即使是openssl_random_pseudo_bytes,因为好的随机数是均匀分布的。这意味着当您发出足够多的请求时,您始终会添加平均睡眠时间。【参考方案4】:

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

ircmaxell 已经回答了为什么这只会让工作更加困难,

但一般来说,防止 PHP 中的计时攻击的解决方案是

/**
 * execute callback function in constant-time,
 * or throw an exception if callback was too slow
 *
 * @param callable $cb
 * @param float $target_time_seconds
 * @throws \LogicException if the callback was too slow
 * @return whatever $cb returns.
 */
function execute_in_constant_time(callable $cb, float $target_time_seconds = 0.01)

    $start_time = microtime(true);
    $ret = ($cb)();
    $success = time_sleep_until($start_time + $target_time_seconds);
    if ($success) 
        return $ret;
    
    // dammit!
    $time_used = microtime(true) - $start_time;
    throw new \LogicException("callback function was too slow! time expired! target_time_seconds: $target_time_seconds actual time used: $time_used");



使用这种方法,您的代码可能是

function timingSafeCompare($a,$b, float $target_time_seconds = 0.01) 
    return execute_in_constant_time(fn() => $a === $b, $target_time_seconds);

缺点是您应该选择一个较大的数字,这意味着相对较多的时间会浪费在睡眠中。在我的笔记本电脑上,我不得不使用 0.2(200 毫秒)来比较 2x 恰好为 1-GiB 的字符串,与Core i7-8565U(我从未听说过的奇怪的 2018 年中端笔记本电脑 CPU)

还有这个循环:

ini_set("memory_limit", "-1");
$s1 = "a";
$s2 = "a";
$append = str_repeat("a",100*1024);
try 
    for (;;) 
        $res = timingSafeCompare($s1, $s2, 0.01);
        $s1 .= $append;
        $s2 .= $append;
    
 catch (\Throwable $e) 
    var_dump(strlen($s1));

大约 65 兆字节/int(65126401)

(但您需要多久对 65MB 以上的字符串进行一次固定时间比较?我想这并不经常)

您可能会认为“然后攻击者可以发送一个巨大的字符串进行比较,并检查抛出异常需要多长时间”但我认为这不会起作用,=== 首先检查两个字符串是否具有相同的长度,并且如果它们具有不同的长度则短路,只有当攻击者可以将 both 字符串的长度设置为足够大以超时时,这种攻击才应该有效

今天我们有本机 hash_equals() 函数来比较具有完全相同长度的字符串,但是 hash_equals() 不会保护您免受不同长度的字符串的影响,而上面的函数会。

【讨论】:

以上是关于随机睡眠可以防止定时攻击吗?的主要内容,如果未能解决你的问题,请参考以下文章

网络安全-重放攻击及其防御

电脑取随机数是啥原理,是真正的随机数吗?

防止跨站攻击——CSRFToken

有没有办法阻止我的 Razer 笔记本电脑随机进入睡眠模式? [关闭]

一个令牌与多个令牌以防止 CSRF 攻击

在 HTTP 连接中使用基于令牌的身份验证时如何防止重放攻击