为什么在PHP的hash_equals()函数中参数的顺序很重要?

13

PHP 5.6引入了hash_equals()函数,用于安全比较密码哈希,并防止时间攻击。其签名为:

bool hash_equals(string $known_string, string $user_string)
根据文档所述,$known_string$user_string必须具有相同的长度,才能有效防止时序攻击(否则将立即返回false,泄露已知字符串的长度)。
此外,文档指出:
“提供用户提供的字符串作为第二个参数而不是第一个参数非常重要。”
对我来说,这个函数在其参数中不是对称的似乎不太直观。
问题是: 为什么用户提供的字符串必须最后提供? 以下是该函数源代码的摘录:
PHP_FUNCTION(hash_equals)
{
    /* ... */

    if (Z_STRLEN_P(known_zval) != Z_STRLEN_P(user_zval)) {
        RETURN_FALSE;
    }

    /* ... */

    /* This is security sensitive code. Do not optimize this for speed. */
    for (j = 0; j < Z_STRLEN_P(known_zval); j++) {
        result |= known_str[j] ^ user_str[j];
    }

    RETURN_BOOL(0 == result);
}

就我而言,对于这两个参数的实现是完全对称的。唯一可能有所不同的操作是XOR运算符。

  • XOR运算符是否可能在非常数时间内执行,取决于参数值?它的执行时间是否取决于参数的顺序(例如,如果第一个参数为零)?

  • 或者,PHP文档中的这个注释是对将来版本实现更改的“保留”?


编辑

正如Morpfh所述,最初的提案实现是不同的:

PHP_FUNCTION(hash_compare)
{
    /* ... */

    /**
     * If known_string has a length of 0 we set the length to 1,
     * this will cause us to compare all bytes of userString with the null byte which fails
     */
    mod_len = MAX(known_len, 1);

    /* This is security sensitive code. Do not optimize this for speed. */
    result = known_len - user_len;
    for (j = 0; j < user_len; j++) {
        result |= known_str[j % mod_len] ^ user_str[j];
    }

    RETURN_BOOL(0 == result);
}

正如你看到的,这个草案实现尝试处理不同长度的哈希,并且对参数进行了不对称处理。也许这个草案实现不是第一个版本。

总结:文档中关于参数顺序的注释似乎是从早期草案实现中遗留下来的。


5
我总是讨厌文档只告诉你该做什么却不解释原因。 - developerwjk
  1. 这是一个 PHP 的问题,为什么要标记为 'c'?
  2. 参数必须按照特定的顺序,因为函数期望参数以这种方式传递。
- user3629249
2
@user3629249,1)它被标记为C,因为PHP是用C实现的,这更多关于PHP的实现而非用法。2)抱歉,我无法理解你最后一条陈述中的任何信息。 - Alex Shesterov
1
一个合同应该按照价值来执行。请遵守它。 - user2864740
1
@user2864740:+1,我也是!这个问题并没有实际价值,只是出于好奇。 - Alex Shesterov
1个回答

5

更新:

请看Rouven Weßling的评论(在本回答下面)。


这更像是猜测而不是答案,但或许你能从中得到一些启示。


一个猜想,就像你提到的,可能是为了未来对函数进行更改时保持向后兼容性,以便(1)在长度相等时返回false;因此易受泄露长度信息的攻击 - 或者(2)其他需要知道哪个是哪个的算法/检查 - 或者(n)...


一个可能的解释是它是实现提案时留下的遗留物:

因此,从提案中可以得出:

用户必须注意,重要的是使用用户提供的字符串(或该字符串的哈希值)作为第二个参数而不是第一个参数。

自提案创建以来一直存在:

例如,可以将其链接到参考文献

在这里,不会在长度相等时返回,而是使用循环useLen。


你可能是对的,这可能是早期实现(或其文档)中剩余的部分。我没有想到去查看历史记录。提案实现(你提供的链接)通过字节逐个比较不同长度的字符串,并且在参数处理方面是不对称的。 - Alex Shesterov
5
我是 RFC 和补丁作者。你说得完全正确。我的原始想法是尝试避免泄漏长度。我保留了参数名称以实现向前兼容(请参见http://marc.info/?l=php-internals&m=139318035405396&w=2)。另外,考虑一下这个问题:http://security.stackexchange.com/questions/49849/timing-safe-string-comparison-avoiding-length-leak,它是在讨论 PHP 实现的过程中提出的。 - Rouven Weßling

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