普通数据校验用hash(),密码等敏感字段必须用password_hash();前者确定性输出适用于签名和缓存,后者自动加盐防暴力破解,混用会导致安全漏洞或验证失败。

PHP接口里用 hash() 还是 password_hash()?
直接说结论:普通数据校验(比如请求签名、参数一致性检查)用 hash();涉及用户密码或需要防暴力破解的敏感字段,必须用 password_hash()。两者设计目标完全不同,混用会导致安全漏洞或验证失败。
常见错误是把 password_hash() 用在 URL 参数签名上——它每次生成结果不同,根本没法比对;反过来用 hash('sha256', $data) 存密码,又容易被彩虹表攻击。
-
hash()是确定性单向散列,输入相同,输出必相同,适合做接口请求签名、缓存 key 生成 -
password_hash()自动加盐且算法可升级(如 bcrypt),只适用于密码存储和验证场景 - 别手写
md5($str)或sha1($str)——这些已不安全,hash()默认支持sha256、sha512等现代算法
接口请求参数签名怎么用 hash_hmac() 防篡改
最常用也最稳妥的方式是 HMAC 签名:服务端和客户端共享一个密钥,对有序参数拼接后做带密钥的哈希。攻击者即使截获请求,没有密钥也无法伪造合法 sign 值。
示例逻辑(假设接口接收 user_id、timestamp、nonce):
立即学习“PHP免费学习笔记(深入)”;
ksort($_GET); // 按键名排序
$data = http_build_query($_GET, '', '&', PHP_QUERY_RFC3986); // 保证编码一致
$sign = hash_hmac('sha256', $data, 'your_api_secret_key');
注意点:
- 必须严格排序参数(
ksort()),否则同一组参数因顺序不同生成不同签名 - 必须统一编码方式,
PHP_QUERY_RFC3986比默认更可靠,避免空格变+等歧义 - 密钥不能硬编码在接口文件里,应从环境变量或配置中心读取
- 务必校验
timestamp是否在允许窗口内(如 ±300 秒),防止重放攻击
hash_equals() 必须用于签名比对,别用 ===
用 === 直接比较两个字符串,在某些 PHP 版本或启用 opcache 时可能触发“时序攻击”——攻击者通过响应时间差异推测出正确签名的部分字节。虽然概率低,但接口一旦暴露在外网,就该按规范来。
正确做法:
$expected = hash_hmac('sha256', $data, $secret);
$provided = $_GET['sign'] ?? '';
if (!hash_equals($expected, $provided)) {
http_response_code(401);
exit('Invalid signature');
}
关键点:
-
hash_equals()是 PHP 内置的恒定时间比较函数,无论字符串是否相等,执行时间都一样 - 它要求两个参数都是字符串,且不能为
null,所以要先做非空判断或默认值兜底 - 如果
$_GET['sign']含非法字符(如换行、控制符),hash_equals()会返回false,这也算一种隐式过滤
要不要 base64 编码哈希值?什么时候用 hash() 的 $raw_output 参数
绝大多数接口场景下,直接用十六进制字符串(默认行为)就够了。hash() 默认返回 64 字符的 sha256 字符串,人类可读、URL 安全、日志友好。
只有当你需要压缩体积或对接其他系统强制要求二进制格式时,才考虑 $raw_output = true,但紧接着就得用 base64_encode() 或 bin2hex() 转成安全字符串传输:
// 不推荐直接传 raw output(含不可见字符,易被中间件截断)
$raw = hash('sha256', $data, true);
// 推荐:转 base64,长度更短(43 字符),且 URL-safe(需再替换 +/ 为 -_)
$encoded = str_replace(['+', '/'], ['-', '_'], base64_encode($raw));
实际项目中,除非有明确性能或协议约束,否则坚持用默认十六进制输出,省去编解码环节,减少出错可能。
真正容易被忽略的是:哈希本身不加密,只是摘要。如果原始参数含敏感信息(如手机号、金额),光签名没用,还得配合 HTTPS 和必要时 AES 加密传输。



















