← PHP sha1() 函数 PHP similar_text() 函数 →

PHP sha1_file()函数详解:文件哈希计算、性能优化与SHA-1安全边界

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

1.函数定位与核心能力

sha1_file()是PHP内置的文件哈希计算函数,使用美国安全散列算法1(SHA-1)对指定文件生成160位摘要值,默认以40位十六进制字符串返回。它的核心价值不在于“计算SHA-1”这个动作本身,而在于以流式方式处理文件——这一点决定了它在大文件场景下与sha1(file_get_contents())存在本质区别。

从PHP5.1.0起,该函数改用流API实现,这意味着它不仅支持本地文件,还能处理HTTP、FTP等封装协议。函数签名如下:

sha1_file(string $filename, bool $binary = false): string|false

成功返回哈希字符串,失败返回false。这一行为从PHP4.3.0延续至今,接口层面保持了极高的稳定性。

2.参数语义

$filename是要计算哈希的文件路径。从PHP5.1.0起支持流封装协议,因此传入https://example.com/data.zip也是合法的。

$binary控制输出格式。默认false返回40字符十六进制字符串;设为true时返回20字节的原始二进制摘要。原始二进制格式在需要将哈希值存入BINARY类型字段、或与其他语言(如Python的hashlib.digest())交互时有用。十六进制格式可读性好,适合日志记录、数据库存储和人工比对。

3.底层机制:流式哈希与内存边界

这是sha1_file()最容易被忽视、却最重要的设计决策。

对比两种常见的文件哈希写法:

// 方式一:sha1_file 流式处理
$hash = sha1_file('/path/to/large-file.iso');

// 方式二:先读取全部内容,再哈希
$hash = sha1(file_get_contents('/path/to/large-file.iso'));

方式二会将整个文件加载到PHP内存中。对于几十兆的图片或文档,这或许可以接受;但当文件达到数百兆甚至数GB时,方式二会迅速耗尽memory_limit,导致脚本终止。

方式一则以块为单位从磁盘(或流)读取数据,将哈希状态保存在内存中逐步更新。PHP手册中的用户注释记录了一个实测数据:对2.6GB的文件,sha1_file()在22秒内完成;而sha1(file_get_contents())因内存不足失败,并浪费了185秒等待超时。

个人经验:在文件上传系统中,如果允许用户上传大于memory_limit的文件,任何形式的file_get_contents()都是隐患。曾遇到过一个头像上传功能,开发者用sha1(file_get_contents($_FILES['avatar']['tmp_name']))生成哈希,在5MB以上文件时触发sha1_file():Filenamecannotbeempty警告,根因其实是upload_max_filesize和post_max_size的限制导致$_FILES的临时文件未正确生成,而file_get_contents()对空路径的静默失败进一步掩盖了真实错误。

4.raw参数的工程用途

raw=true返回的20字节二进制摘要,在这些场景中有明确价值:

数据库存储:MySQL的BINARY(20)比CHAR(40)节省一半空间。对于存储百万级文件哈希的系统,索引体积的差异会在查询性能上体现。

Base32编码兼容:像Gnutella/LimeWire这类P2P协议使用Base32编码的SHA-1作为文件标识。Base32编码的输入正好是原始二进制摘要。

跨语言一致性:Python的hashlib.sha1(file.read()).digest()返回的就是20字节二进制摘要。当PHP与Python服务需要比对同一文件的哈希时,统一使用二进制格式可以避免十六进制大小写导致的意外不匹配。

5.版本差异

PHP版本 变化
PHP4.3.0 函数引入
PHP5.0.0 添加raw_output参数
PHP5.1.0 改用streamsAPI,支持URL封装协议
PHP5.1.2 Hash扩展默认可用,hash_file('sha1',...)作为替代方案出现
PHP7.x/8.x 接口稳定,无破坏性变更

从PHP5.1.2起,hash_file('sha1',$file)与sha1_file($file)产生相同结果。两者在底层共享哈希实现,差异仅在于函数入口。如果你的代码需要支持多种哈希算法(SHA-1、SHA-256、xxHash等),使用统一的hash_file()接口更利于后期切换。

6.常见错误与异常处理

sha1_file()的失败模式相对“温和”——返回false,而不是抛出异常。这既是优点(不会中断流程),也是缺点(容易被忽略)。

典型失败场景包括:

文件不存在或不可读:返回false,可能伴随E_WARNING。
路径为空字符串:在PHP8中行为更严格,某些版本会触发警告。
内存或临时文件问题:上传文件超出upload_max_filesize时,$_FILES['file']['tmp_name']可能为空,传入sha1_file()后触发Filenamecannotbeempty警告。

推荐的防御性写法:

function safe_sha1_file(string $path): ?string
{
    if (!is_file($path) || !is_readable($path)) {
        return null;
    }
    $hash = @sha1_file($path);
    return $hash === false ? null : $hash;
}

在现在PHP中,更规范的做法是结合异常抛出:

function sha1_file_or_fail(string $path): string
{
    if (!is_file($path)) {
        throw new \InvalidArgumentException("文件不存在: {$path}");
    }
    $hash = sha1_file($path);
    if ($hash === false) {
        throw new \RuntimeException("无法计算文件哈希: {$path}");
    }
    return $hash;
}

7.SHA-1的安全边界:该用还是不该用

SHA-1在密码学意义上已被攻破。2017年Google和CWI发布的SHAttered攻击首次实现了实际的SHA-1碰撞:两份内容不同的PDF文件产生了相同的SHA-1哈希。主流浏览器和CA机构早已停止支持基于SHA-1的SSL/TLS证书。

但这不意味着sha1_file()在文件完整性校验场景中毫无价值。需要区分两类需求:

对抗性场景(不应使用SHA-1):数字签名、证书、防篡改的软件分发。攻击者有能力构造碰撞时,SHA-1的160位摘要无法提供足够的安全边界。这类场景应使用SHA-256或SHA-3。

非对抗性场景(SHA-1仍可胜任):文件去重、上传完整性校验、缓存键生成。在这些场景中,主要风险是“意外碰撞”而非“恶意碰撞”。两个不同文件产生相同SHA-1的概率极低,远比磁盘损坏的概率小。对于非对抗性用途,SHA-1的碰撞攻击不具备实际威胁,因为攻击者没有动机去伪造一个与合法文件碰撞的恶意文件。

个人建议:新项目在文件哈希场景中直接使用hash_file('sha256',$file),代价是计算时间略长、哈希值更长,但消除了将来安全审查时的顾虑。如果受限于遗留系统的存储格式或协议兼容性必须使用SHA-1,应当在文档中明确标注“仅用于非安全关键的完整性校验”。

8.性能对比与选型

在文件哈希场景中,性能主要由磁盘I/O决定,算法本身的差异在常见文件大小下并不显著。StackOverflow上的实测数据(两个1.2MB文件各比对100次)显示:sha1_file耗时1.86秒,md5_file耗时1.6秒,file_get_contents直接比对耗时0.5秒但峰值内存为sha1_file的约19倍。

file_get_contents在速度上的优势来自它没有哈希计算开销,但代价是内存占用随文件大小线性增长。对于大文件,sha1_file()和md5_file()的内存占用基本恒定,因为它们以流方式处理。

如果文件去重是核心需求且对速度敏感,可考虑PHP8.1+内置的xxHash(hash('xxh128',...))或通过FFI调用原生xxHash库。纯PHP实现的xxHash反而比SHA-1更慢,因此除非使用FFI或C扩展,否则SHA-1在“流式哈希”这一类别中仍有不错的性价比。

9.与相邻函数的职责对比

sha1()处理字符串而非文件。不要用sha1(file_get_contents($file))替代sha1_file(),除非你能确保文件大小远低于memory_limit。

md5_file()生成128位摘要,速度略快于sha1_file(),但碰撞概率更高。在非对抗性去重场景中两者均可,md5的速度优势在大量小文件批量处理时可能体现。

hash_file('sha1',$file)与sha1_file()结果一致,优势在于算法可通过变量动态指定,便于将来迁移到SHA-256。

hash_init()+hash_update()+hash_final()提供增量哈希能力,适合边读取边计算或分块处理的场景。

10.现在框架中的惯用写法

Laravel中通常使用hash_file('sha256',$path)或通过Storage门面封装。Symfony的文件组件提供了更上层的抽象。在框架代码中直接调用sha1_file()的场景已不多见,更多出现在需要对底层文件做快速指纹计算的自定义服务中。

11.练习与思考

练习一:编写一个函数,接收两个文件路径,先比较文件大小,再比较SHA-1哈希,返回是否相同。思考为什么不建议直接file_get_contents比对。

练习二:用sha1_file()生成上传文件的哈希,存入数据库的BINARY(20)字段。实现时使用raw=true,并处理哈希冲突时的文件大小复核逻辑。

思考题:如果一个文件托管服务用SHA-1作为文件唯一标识,攻击者能否利用SHA-1碰撞构造两个不同的文件,让服务端将它们视为同一个文件?这个攻击在中需要多大的计算资源?

12、延伸阅读与参考文献

PHP官方手册—sha1_file
链接:https://www.php.net/function.sha1-file
说明:函数签名、参数说明、版本变更记录,包含用户贡献的流式处理性能实测注释。

PHP官方手册—hash_file
链接:https://www.php.net/function.hash-file
说明:多算法统一接口,用于需要动态切换哈希算法或同时支持SHA-256的场景。

SHAttered项目
链接:https://shattered.io
说明:首个公开的SHA-1碰撞攻击实际案例,两份PDF文件的SHA-1哈希相同。用于理解SHA-1在对抗性场景中的安全边界。

StackOverflow—PHPsha1_file内存测试
链接:https://stackoverflow.com/questions/2293902/is-sha-sufficient-for-checking-file-duplication-sha1-file-in-php
说明:1.2MB文件比对场景下的时间与内存实测数据,以及去重场景中SHA-1适用性的讨论。

← PHP sha1()函数哈希原理、安全边界与password_hash替代方案 PHP similar_text()函数深度教程:字符串相似度计算的算法边界与工程取舍 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号