← PHP setlocale() 函数 PHP sha1_file() 函数 →

PHP sha1()函数哈希原理、安全边界与password_hash替代方案

著
原创 2026-10-11 PHP 已有人查阅

sha1()的计算目标

sha1()对输入字符串执行SHA-1(SecureHashAlgorithm1)单向散列运算,返回固定长度的“指纹”。SHA-1产生160位(20字节)的输出,默认以40个十六进制小写字符表示。

<?php
// 代码号学习PHP:基础哈希计算
echo sha1("Hello PHP");
// a02e266c6f3a8ff0c4250e502828c4ebf179d252

// 第二个参数为 true 时返回原始二进制
echo bin2hex(sha1("Hello PHP", true));
// a02e266c6f3a8ff0c4250e502828c4ebf179d252

第二个参数raw控制输出格式:true返回20字节的原始二进制字符串,false(默认)返回40字符的十六进制表示。两者代表相同的摘要值,区别仅在编码形式。

空字符串与类型转换的边界行为

sha1()对输入执行字符串强制转换。理解这些转换规则对调试认证逻辑尤为重要。

<?php
// 代码号学习PHP:空值哈希的同一性
echo sha1("");     // da39a3ee5e6b4b0d3255bfef95601890afd80709
echo sha1(NULL);   // da39a3ee5e6b4b0d3255bfef95601890afd80709
echo sha1(false);  // da39a3ee5e6b4b0d3255bfef95601890afd80709

空字符串、null和false经字符串转换后均为空字符串,得到相同的哈希值。这导致一个经典:如果系统中两个用户的密码均为空,sha1($stored)==sha1($input)会返回true。PHP手册的社区注释建议对空密码单独判断,而非依赖哈希比较。

数值类型则产生不同结果:sha1(0)的哈希为b6589fc6...,与sha1("0")一致,但与sha1(false)不同。sha1(1)与sha1(true)则得到相同哈希,因为true转换为字符串"1"。

PHP版本差异:一个真实的兼容性案例

sha1()本身自PHP4.3.0起保持稳定,但输入字符串的解析可能因PHP版本而异。StackOverflow上的一个案例展示了这个问题:

<?php
define('SECRET', "vJs;ly-W\XDkD_2'-M7S2/ZRRBobxt5");
echo sha1(SECRET . '...');

同一段代码在PHP5.1.6和PHP5.3.2上产生不同结果。根因是双引号字符串中转义序列解析规则的变化:PHP5.3开始识别\X为十六进制转义(此前仅识别\x),导致字符串内容不同。这是PHP字符串解析器的变更,而非sha1()函数本身的行为差异。

单元测试与边界验证

基础一致性测试:

<?php
// 代码号学习PHP:sha1 与 hash('sha1') 一致性
$testCases = ["", "a", "Hello PHP", str_repeat("x", 1000)];

foreach ($testCases as $input) {
    $sha1Result = sha1($input);
    $hashResult = hash('sha1', $input);
    assert($sha1Result === $hashResult, "Mismatch for: {$input}");
}

// raw 输出与 hex 输出的关系
$hex = sha1("test");
$raw = sha1("test", true);
assert(bin2hex($raw) === $hex);

sha1()与hash('sha1',$string)产生相同的结果,两者底层调用同一实现。

安全边界:为什么不应使用sha1()存储密码

sha1()的设计目标是快速计算,这一特性在密码存储场景中构成了根本性缺陷。SHA-1在现在GPU上的计算速度可达每秒数十亿次,这意味着破解和字典攻击的成本极低。

OWASP的移动应用安全文档明确指出:SHA-1和MD5存在的碰撞攻击,NIST已将其从所有安全用途中弃用。微软的PowerShell分析规则同样将SHA-1标记为“密码学破解的哈希算法”,仅在遗留系统向后兼容的场景下允许使用。

正确做法:使用password_hash()配合PASSWORD_DEFAULT(当前为bcrypt)或PASSWORD_ARGON2ID,并使用password_verify()验证。

<?php
// 代码号学习PHP:密码存储的正确方式
$hash = password_hash($userInput, PASSWORD_DEFAULT);
// 存储 $hash 到数据库

if (password_verify($userInput, $storedHash)) {
    // 验证通过
}

sha1()与hash()的职责对比

维度 sha1() hash('sha1',...) hash_hmac()
算法支持 仅SHA-1 全部已注册算法 HMAC密钥哈希
引入版本 PHP4.3.0 PHP5.1.2 PHP5.1.2
用途 兼容旧代码、非安全校验 通用哈希计算 带密钥的消息认证

hash()函数提供了统一的接口,支持SHA-256、SHA-512、SHA-3等更安全的算法。对于新项目,hash('sha256',$data)是SHA-1的直接替代,输出64字符十六进制。如果需要密钥验证(如API签名),应使用hash_hmac()而非将密钥拼接进消息再哈希——后者容易受到长度扩展攻击。

常见误区速查

误区 实际行为
sha1("")返回空字符串 返回da39a3ee...,空输入的哈希
sha1()可以用于密码存储 速度过快,应使用password_hash()
sha1($data.$key)安全等价于HMAC 易受长度扩展攻击,应用hash_hmac()
raw输出可以直接用于显示 二进制字符串含不可打印字符,通常需bin2hex()转换
十六进制输出大小写敏感 sha1()默认返回小写,输入计算时统一

现在框架与实践建议

Laravel、Symfony等框架的密码认证层均使用password_hash(),不再依赖sha1()。在需要文件完整性校验的场景中,sha1_file()或hash_file('sha256',...)是合适的工具。sha1()的合理使用范围限于:与遗留系统交互时的兼容哈希、非安全场景的数据指纹(如缓存键生成)、以及测试中构造预期值。

延伸练习

练习一:验证sha1()对空字符串、null、false和0的哈希结果,解释为什么sha1("")和sha1(NULL)相同但sha1(0)不同。

练习二:编写一个函数,接收用户密码,先使用sha1()模拟旧系统的验证逻辑,然后在首次成功登录时用password_hash()重新哈希存储,实现平滑迁移。

练习三:对比sha1($data)与hash_hmac('sha1',$data,$key)在密钥泄露场景下的安全性差异,理解长度扩展攻击的基本原理。

参考文献

PHP 官方手册:sha1()
https://www.php.net/manual/en/function.sha1.php
官方函数文档,包含函数签名、raw参数说明、空字符串哈希的社区讨论,以及关于长度扩展攻击的安全提醒。

PHP 官方手册:Hash 函数
https://www.php.net/manual/en/ref.hash.php
哈希函数总览,列出hash()、hash_hmac()、hash_file()等函数的职责,说明 SHA-1 在hash()中的可用性。

OWASP MASWE-0008: Improper Hashing
https://mas.owasp.org/MASWE/MASVS-CRYPTO/MASWE-0008/
OWASP 移动应用安全文档,明确SHA-1因碰撞攻击被NIST弃用,不得用于安全敏感场景,密码存储必须使用专用KDF。

Microsoft PowerShell AvoidUsingBrokenHashAlgorithms
https://learn.microsoft.com/zh-cn/powershell/utility-modules/psscriptanalyzer/rules/avoidusingbrokenhashalgorithms
微软分析规则文档,将SHA-1和MD5标记为密码学破解算法,仅在遗留系统兼容场景下允许使用。

PHP 手册社区注释:sha1("") 行为
https://www.php.net/manual/en/function.sha1.php#34043
官方手册社区注释,记录空字符串、null、false和数值类型哈希的精确值,以及空密码比较的陷阱和修正方案。

← PHP setlocale()函数:区域设置原理、线程安全陷阱与intl替代方案 PHP sha1_file()函数详解:文件哈希计算、性能优化与SHA-1安全边界 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号