在MySQL中比较字符串是否容易受到时间攻击的影响?

7

我正在为用户密码部署一种经典的哈希保护机制。在登录时,提交的密码会被加盐、散列化,然后与已存储在数据库中的散列值进行比较。

但是,与其使用 PHP 函数调用来比较现在已经散列化的用户输入和存储的散列值不同,比较是在数据库中完成的,更确切地说,是使用一个 WHERE 子句(注意:由于各种原因,此时已经知道了盐,但尚未知道密码)。

由于用户名是唯一的,因此以下查询有效地确定用户名+密码是否匹配:

SELECT * FROM `users` WHERE `password`='$password_hash' AND `username`='$username';

这种方法是否容易受到时间攻击的影响?
编辑:不必担心SQL注入问题,已经得到解决。

取决于攻击者,但不太可能。你更容易受到SQL注入!的攻击。你应该使用参数化查询。 - Jay Blanchard
当前正在开发的查询比这个更复杂,当然; 我只是给出了一个简短的例子来说明这个问题。 - John Weisz
3个回答

11

原则上,字符串比较(和/或索引查找)可能会泄露密码哈希存储在数据库中的前导字节与输入密码计算出的哈希值之间有多少个相同的字节。

原则上,攻击者可以利用这一点逐字节地迭代学习密码哈希的前缀:首先找到一个哈希值,它与数据库中的哈希值共享其第一个字节,然后是共享前两个字节的哈希值,以此类推。

不,这几乎肯定不重要。

为什么?好吧,有很多原因:

  1. 定时攻击可能会允许攻击者学习用户密码哈希的一部分。 精心设计的密码哈希方案(使用盐和 密钥延展),即使攻击者知道整个密码哈希,也应该保持安全(当然,假设密码本身不容易被猜测)。 因此,即使定时攻击成功,密码本身也将是安全的。

  2. 要执行攻击,攻击者必须提交其已知哈希值的密码。 哈希值取决于盐。 因此,除非攻击者早已知道盐,否则攻击是不可能的。

    (密码哈希方案的大多数安全分析中,确实假设盐是公共信息。 然而,这仅因为这种分析假设上述最坏情况,即攻击者已经获得了整个用户数据库,包括盐和哈希值等。 如果攻击者尚未知道哈希值,就没有理由假设他们会知道盐。)

  3. 即使攻击者知道盐,在执行上述迭代攻击之前,他们需要生成哈希值以具有所需的前缀的密码。 对于任何安全的哈希函数,唯一可行的方法是通过试错,这意味着所需的时间随前缀长度呈指数增长。

  4. 实际上,这意味着为了提取足够多的哈希位来执行离线暴力攻击(不必全部提取;只需比密码中的有效熵更多),攻击者需要进行与破解密码本身所需的计算量相同的计算。对于设计良好的密码哈希方案和安全选择的密码,这是不可行的。 迭代攻击原则上可以使攻击者能够在本地完成大部分暴力计算,而仅向您的系统提交相当少量的密码。然而,即使如此,也仅在他们从每个提交的密码那里收到详细可靠的时间信息时才成立。实际上,真正的定时攻击效率极低,并且需要许多(通常数千或数百万)查询才能产生任何有用信息。这很可能会抵消定时攻击可能为攻击者提供的任何性能优势。 如果使用适当的密钥拉伸密码哈希方案,则此点会被放大,因为这种方案是故意设计为慢速的。因此,与首次哈希密码相比,数据库中的字符串比较很可能需要花费可忽略的时间,并且由此引起的任何时间变化都将消失在噪声中。

截至2014年11月20日,两个提交的答案都是高质量的,并提供了描述良好的解决方案。我选择这个作为被接受的答案,因为在我看来,它为将来的参考提供了更多的细节,并强调了一些可能的时间攻击的附加危险 - 即使另一个帖子是对问题更直接的回答。 - John Weisz

1
如果您在users表中的(username, password)上创建一个复合索引,并将查询从SELECT *更改为SELECT COUNT(*) AS matching_user_count,则可以大大改善匹配和不匹配的查询所需时间。如果哈希值都是相同长度,也会有所帮助。
如果所有这些查询花费相同的时间,将使计时攻击变得更加困难。显然,您可以通过在每个查询上休眠一个伪随机的时间来做更多的工作以打败计时攻击。请尝试此操作:
SELECT COUNT(*) AS matching_user_count, SLEEP(RAND()*0.20) AS junk
  FROM `users` 
 WHERE `password`='$password_hash'
   AND `username`='$username'

它将为每个查询添加0到0.2秒之间的随机时间。这种随机性将主导执行索引WHERE子句的近乎恒定的时间。

6
这并不是处理时序攻击的安全方法。虽然它会使时序攻击更加困难,但并不能真正解决问题。http://security.stackexchange.com/questions/96489/can-i-prevent-timing-attacks-with-random-delays https://dev59.com/wl4c5IYBdhLWcg3wCGb2#28406531 - d0nut

0

一般情况下防止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}");
}

通过这个,你可以做到

$rows = execute_in_constant_time(function () use ($db, $password_hash, $username) {
    return $db->query("SELECT * FROM `users` WHERE `password`='$password_hash' AND `username`='$username';")->fetchAll();
}, 0.01);


从那里,你可以微调0.01毫秒或100毫秒的0.1,并且函数将使用确切的时间长度,无论多少个字节是正确还是错误...除非您选择了执行时间过短,否则会收到LogicException...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接