必须用hash_file('sha256', $localpath)计算sha256哈希并与服务端值严格比对(===),前置检查file_exists()和is_readable(),失败时排查权限、路径编码或空文件;禁用md5_file();推荐流式校验避免大文件i/o浪费,并用rename()原子化替换确保安全。

PHP 8.0 下载服务器文件后,不能只靠 file_exists() 或 filesize() 判断“文件存在且大小一致”就认为完整——这漏掉了数据损坏、传输截断、磁盘写入失败等真实风险。必须做内容级校验。
用 hash_file() 校验单个文件的 SHA256
这是最常用、最轻量的完整性验证方式,适用于下载后立即校验场景:
-
hash_file('sha256', $localPath)返回 64 字符十六进制字符串,需与服务端提供的可信哈希值严格比对(===) - 若返回
false,常见原因有:文件被其他进程锁定、权限不足、路径含中文未正确编码、或文件实际为空(touch创建但未写入) - 不要用
md5_file()替代——PHP 8.0 中它仍可用,但碰撞概率高,不适用于安全敏感场景(如密钥、证书、安装包) - 示例中务必加
is_readable($localPath)前置检查,否则hash_file()静默失败并返回false,容易误判为哈希不匹配
下载时边写边校验(流式校验)
避免先完整写入磁盘再哈希——大文件(>100MB)会浪费 I/O 和内存,且失败后需清理临时文件:
- 用
fopen($url, 'rb')或cURL的CURLOPT_WRITEFUNCTION回调接收字节流 - 在回调中持续更新
hash_init('sha256')上下文:hash_update($ctx, $data) - 下载完成后调用
hash_final($ctx)得到最终哈希,与预期值比对 - 关键点:必须在
fwrite()成功后才更新哈希,否则写入失败(如磁盘满)会导致哈希与实际文件内容不一致 - 注意:PHP 8.0 的
hash_update_stream()不支持远程流(php://input或https://),只能用于本地文件句柄
校验失败时如何安全处理
仅抛出异常或记录日志不够——用户可能重试导致重复下载,或残留损坏文件干扰后续逻辑:
- 下载前生成唯一临时名(如
temp_'.uniqid().'.part),校验通过后再rename()为正式名;失败则unlink()临时文件 - 若校验失败,检查
http_response_code()和cURL的CURLINFO_HTTP_CODE—— 有时服务端返回 200 但响应体是 HTML 错误页(如 Nginx 502 页面),此时hash_file()计算的是错误页哈希 - 不要用
@unlink($localPath)静默删除;应先chmod($localPath, 0600)降低权限,再unlink(),防止竞态条件下被恶意读取残留内容 - 对于关键文件(如数据库迁移脚本),建议额外校验
filemtime()是否在合理时间窗口内,防缓存污染
真正难的不是算哈希,而是确保参与校验的每个环节都不可绕过:从下载协议(优先用 HTTPS)、临时文件权限、到哈希比对是否用了严格相等(===)和恒定时间比较(hash_equals())。漏掉任意一环,所谓“完整性”就只是自我安慰。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











