flawopen.com/类型转换/PHP ==
PHP == 适合比较哈希吗?
不适合。使用 == 时,PHP 会按数值比较两条数字字符串,因此科学计数法形式的不同字符串在 PHP 8 中仍可能相等。先验证输入类型。精确值使用 ===;秘密令牌或 MAC 使用 hash_equals(),避免依赖内容的时间泄露。
<?php // Both strings are numeric and represent zero $storedHash = "0e462097431906509019562988736854"; $submittedHash = "0e987654321"; // == returns true on PHP 7 and PHP 8 if ($storedHash == $submittedHash) { login(); } // Request data may also have the wrong type: $submitted = $_POST['token'] ?? null; if ($storedHash == $submitted) { login(); } // ?token[]=x supplies an array, not a string.
<?php
$submitted = $_POST['token'] ?? null;
if (!is_string($submitted)) {
http_response_code(400);
exit;
}
// For secret tokens or MACs:
$tokenIsValid = hash_equals($storedToken, $submitted);
if ($tokenIsValid) {
login();
}
// For non-secret identifiers, strict
// comparison checks both type and value:
$idsMatch = ($expectedId === $submitted);
// Passwords require password_verify(),
// not a hand-written hash comparison:
$passwordIsValid = password_verify($plain, $storedPasswordHash);
“Magic Hash”问题
PHP 8 前后都会按数值比较两条数字字符串。以 0e 开头且后续只有数字的十六进制哈希,是数值为零的有效科学计数法字符串。例如 0e462097431906509019562988736854 与 0e987654321 虽然不同,但都表示零,因此用 == 比较相等。PHP 8 改变了字符串与数字的比较规则,没有改变两条数字字符串之间的这种比较。
PHP 8 改变了数字与非数字字符串的比较:现在比较它们的字符串形式,而不会把非数字字符串当作零。它没有消除两条数字 0e… 字符串之间的经典问题。无论 PHP 版本如何,都不要用 == 做身份验证。
请求类型也是信任边界的一部分
请求参数不一定是字符串。像 ?token[]=x 这样的查询会生成数组。将其传入面向字符串的 API,可能根据 PHP 版本和函数触发类型错误或非预期路径。在比较或调用 hash_equals() 前,先在请求边界确认它是字符串。
可靠防护有两步:在边界验证输入类型,再选择符合安全需求的比较方式。=== 检查类型和值;hash_equals() 比较秘密字符串时避免内容相关的时间泄露。
常见问题
没有。PHP 8 改变了字符串与数字的比较,但两条数字字符串仍按数值比较。不同的 0e… 字符串仍可能相等。精确类型值用 ===,秘密值用 hash_equals(),并在所有支持版本中验证输入类型。
PHP 将 hash_equals() 说明为可防止字符串内容通过执行时间泄露;普通比较耗时可能取决于差异出现的位置。先传入已知秘密,再传入用户字符串。密码应使用 password_verify()。
JavaScript 的 == 也会带来比较陷阱,因此通常建议默认使用 ===。PHP 中这种特定 Magic Hash 现象来自十六进制哈希与科学计数法的比较。