
password_verify() 通过恒定时间字符串比较(而非依赖哈希计算耗时)防止计时攻击,其安全性核心在于逐字节比对完整哈希值,确保执行时间与输入差异位置无关。
`password_verify()` 通过恒定时间字符串比较(而非依赖哈希计算耗时)防止计时攻击,其安全性核心在于逐字节比对完整哈希值,确保执行时间与输入差异位置无关。
在现代 Web 应用的身份认证中,密码验证环节极易成为计时攻击(Timing Attack)的突破口。攻击者可通过精确测量 password_verify() 的响应时间,推断出密码哈希的字节匹配程度(例如:第一个字节不匹配时函数返回极快,第二个字节不匹配时略慢……),从而逐步暴力还原出正确哈希——进而反推原始密码或绕过验证逻辑。
需要明确的是:password_verify() 的抗计时能力与 password_hash() 中的 cost 参数无直接关系。cost(如 bcrypt 中的 log_rounds)仅控制哈希计算本身的计算强度(例如重复加密次数),目的是延缓暴力破解速度,但它会导致不同密码的 password_verify() 总执行时间显著波动——因为每次验证都需重新执行完整、耗时的密钥派生过程(KDF)。这种波动恰恰是计时攻击的温床,但 PHP 并未(也无法)通过调节 cost 来“匀速化”整个验证流程。
真正起防护作用的是 password_verify() 内部实现的恒定时间(constant-time)二进制比较算法。它严格按以下方式工作:
- 解析存储的哈希字符串(如 $2y$10$...),提取其中嵌入的算法、salt 和 cost 参数;
- 使用完全相同的参数,对用户提交的明文密码重新执行一次完整的哈希运算,生成待比对的候选哈希;
- 对两个哈希结果(存储哈希 vs 新计算哈希)进行逐字节异或(XOR)累加比较,而非传统短路比较(== 或 strcmp()):
// 简化示意:实际 PHP 内部使用更严谨的 C 实现 function hash_equals_constant_time($a, $b) { if (strlen($a) !== strlen($b)) { return false; } $result = 0; for ($i = 0; $i < strlen($a); $i++) { $result |= ord($a[$i]) ^ ord($b[$i]); // 逐字节异或,累积非零位 } return $result === 0; // 仅当所有字节完全相等时 result 为 0 } - 无论两个哈希在第 1 位还是最后 1 位才出现差异,该算法始终遍历全部字节,且每轮操作的 CPU 指令数、内存访问模式均保持一致,从根本上消除时间侧信道。
⚠️ 注意事项:
立即学习“PHP免费学习笔记(深入)”;
- 切勿自行实现比较逻辑:永远使用 password_verify(),而非 ==、=== 或 hash_equals()(后者虽也是恒定时间,但仅适用于已知长度的原始哈希值比对,不处理密码哈希解析与重计算);
- 哈希存储格式必须完整:password_hash() 返回的字符串(含算法标识、cost、salt 和 hash)必须原样存储,任何截断或编码改动都会导致 password_verify() 解析失败或降级为非安全比较;
- cost 仍需合理设置:尽管它不防计时攻击,但过低的 cost 会使哈希计算过快,削弱对离线暴力破解的防御力;建议使用 PASSWORD_DEFAULT 并定期随硬件升级调整。
总结而言,password_verify() 的安全性是双重保障:前端靠恒定时间比较封堵侧信道,后端靠强哈希算法(bcrypt/Argon2)提升计算成本。开发者只需遵循最佳实践——使用 password_hash() 生成、password_verify() 验证、原样存储哈希字符串——即可在无需理解底层细节的前提下,获得工业级的密码验证安全保障。











