PHP中用于检查哈希相等性的strcmp vs. == vs. ===的比较

17

我正在使用 PHP 中的 crypt() 函数来哈希密码,并尝试找出在执行密码检查时测试等式的最安全方法。

我看到有三个选项:

选项1 - 双等号比较

function checkPassword($hash, $password)
{
    return crypt($password, $hash) == $hash;
}

选项2 - 三个等号

function checkPassword($hash, $password)
{
    return crypt($password, $hash) === $hash;
}

选项3 - strcmp()

function checkPassword($hash, $password)
{
    return strcmp(crypt($password, $hash), $hash) === 0;
}

我的直觉告诉我选项1不好,因为它缺乏类型检查,而选项2或3可能更好。但是,我无法确定是否存在一种特定情况下 ===strcmp 会失败。哪个选项对于这个目的最安全?


1
阅读链接:http://raz0r.name/vulnerabilities/simple-machines-forum/ - user956584
@Userpassword 有趣的 bug!我想知道 PHP 如何处理像 $2a$10$...$ 这样的字符串当作数字时会发生什么... - Polynomial
1
如果你只想可靠地比较哈希值,简单地使用 === 即可。如果你真的关心安全性和潜在的时间攻击(即使在网络抖动的情况下),你应该查看此讨论或使用 hash_equals() 函数(PHP 5.6+)。另外,你也可以参考这个链接:https://github.com/delight-im/Faceless/pull/5。 - caw
请查看此链接:http://danuxx.blogspot.com/2013/03/unauthorized-access-bypassing-php-strcmp.html。 - Rptk99
4个回答

21

在安全性方面,我更喜欢使用===运算符。===确保两个操作数完全相同,而不尝试适应某些强制转换以“帮助”比较成功匹配-尽管在开发过程中可能有所帮助,但这对于像PHP这样的弱类型语言来说是不必要的。

当然,其中一个操作数是可信的。来自数据库的哈希值是可信的,而用户输入则不可信。

在特定情况下使用==可能没有风险,我们可以犹豫一会儿得出结论。但例如

  "0afd9f7b678fdefca" == 0 is true
  "aafd9f7b678fdefca" == 0 is also true

由于PHP尝试将“哈希”转换为数字(可能使用atoi),这会导致0。虽然crypt返回0的可能性很小,但我更喜欢通过使用===来最大化密码不匹配的情况(并回答支持电话),而不是使用==允许我没有考虑过的罕见情况。

至于strcmp函数,如果不同则返回<0>0,如果相等则返回0。

  strcmp("3", 0003) returns 0
  strcmp("0003", 0003) returns -3
这并不奇怪。一个字面上的0003实际上是一个整数3,由于strcmp期望一个字符串,因此3将被转换为"3"。但这表明在这种情况下可能会发生一些转换,因为strcmp是一个函数,而===是语言的一部分。
所以,在这种情况下,我的首选是===(它比==更快)。

这是我见过的最明智的答案。我同意类型和值都很重要,以避免“转换助手”出现问题。 - Polynomial
请注意,当测试使用诸如“password_hash”之类的强函数产生的哈希值时,应使用hash_equals() - Polynomial

7

您应该使用PHP内置的hash_equals()函数。没有必要自己编写函数。hash_equals()将返回一个布尔值。

我认为通常不建议使用==或===比较字符串,更不用说哈希后的字符串了。


1
这现在是由password_hash生成的值的正确答案,但在提问时,hash_equals在主流PHP中并不存在,并且它不适用于平面哈希(本来就不应该使用,但有时对于遗留系统是必需的)。 - Polynomial

0

那是不正确的,请查看函数的定义。根据PHP:

如果str1小于str2,则返回<0;

如果str1大于str2,则返回>0,

如果它们相等,则返回0

如果str1小于str2,则它返回小于0。请注意短语“小于”,它不仅返回-1,而是任何负值。当str1大于str2时,发生同样的情况,但它返回一个正的非零值。它返回一个可以是1或之后任何数字的正值。

strcmp()返回的是两个字符串从最后一个被发现相似的字符开始的差异。

这里有一个例子:

$output = strcmp("red", "blue");

变量$output将包含一个值为16的值


-5

我认为在你的情况下使用==就足够了。

==不考虑类型,检查相等性,而===检查相等性和类型。

1 == "1" = True

1 === "1" = False

由于我们不太关心类型,所以我建议保持简单,选择==


1
我知道=====之间的区别,但我正在努力弄清楚是否有我忽略的特定安全敏感情况。例如,是否有不使用strcmp的原因? - Polynomial
既然您不关心二进制安全比较,我不会使用strcmp。您只关心字符串是否在语义上相同,而不关心它们有多不同。 - Ian P
只要哈希算法不改变,我相信如果是这种情况,没有人会注意到在没有强制转换的情况下比较“==”的问题。 - gries
如果(“asdf”== 0) die(“d'oh\n”); - Antti Rytsölä
1
@IanP 不,那不是问题所在,他需要一个安全比较两个字符串的函数,我们不应该关心这些字符串“应该”或“可能”看起来像什么,因为这些东西可能会改变。 - gries
显示剩余5条评论

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