文件头校验(魔数)与hash_file()哈希校验必须联合使用:先通过魔数确认文件类型合法性,再用哈希验证内容完整性,顺序不可颠倒,任一失败均需立即清理临时文件。

hash_file() 不能代替文件头校验,二者解决的问题完全不同
只比哈希值一致,不代表文件类型合法;只看文件头(魔数),也不能证明内容没被篡改。比如攻击者可以把一段 PHP WebShell 的二进制内容拼在合法 JPG 文件头后面,hash_file() 会因内容不同而失败,但若他伪造一个“看起来像 JPG”的头部+完整恶意 payload,fread() 检查魔数可能通过,而实际文件根本无法当图片渲染——这就是为什么必须联合使用。
先读魔数再算哈希:顺序错了会漏掉关键风险
真实上传流程中,应该在文件落盘前就完成魔数校验,而不是等 move_uploaded_file() 完成后再去读。否则恶意文件已写入临时目录,哪怕后续哈希不匹配,也存在被重放或触发解析的风险。
- 用
fopen($tmpFile, 'rb')打开上传的临时文件,必须是二进制模式,否则 Windows 下换行符可能被转义,魔数比对失效 - 用
fread($handle, 16)读前 16 字节,转换为十六进制字符串后与标准魔数比对(如 JPG 是'ffd8ff',PNG 是'89504e47') - 魔数通过后,再调用
hash_file('sha256', $tmpFile)计算哈希——此时文件还在临时位置,未移动到业务目录,出错可直接unlink() - 如果魔数校验失败,立刻返回 400 错误并删除临时文件,不要留任何中间状态
hash_file() 返回 false 不代表哈希是 "false" 字符串
hash_file() 在路径不存在、权限不足、符号链接断裂或磁盘满时都会返回 false,不是哈希值本身。很多代码直接拿这个结果做 === 比较,导致校验逻辑静默跳过。
- 必须前置检查:
file_exists($path)和is_readable($path),两者都为 true 才能继续 - 用
realpath($path)展开路径,避免相对路径或软链指向不可达位置 - 注意 Web 服务器用户(如
www-data)是否对上传临时目录有读权限——PHP 默认的upload_tmp_dir有时权限设得太严 - 不要用
md5_file()做生产校验,MD5 已被证实可碰撞,至少用sha256
大文件场景下,别让 hash_file() 卡住整个请求周期
单个 5GB 视频文件走 hash_file('sha256', $path),在机械硬盘上可能耗时 90 秒以上,期间 PHP 进程阻塞,Nginx 可能先超时断连,前端看到的是 504 而不是校验失败。
- 分片上传时,建议每片单独计算哈希,客户端传上来时附带该片的
sha256,服务端只验证当前片,不等全部接收完 - 合并完成后,再对最终文件执行一次全量
hash_file(),作为最终一致性兜底 - 如果必须实时校验大文件,改用流式哈希上下文:
hash_init('sha256')+ 多次hash_update($ctx, $chunk),每次读 8192 字节,避免单次 I/O 压力过大 - 注意:
hash_init()必须在fopen('rb')后立即调用,且hash_final($ctx)只能调一次,重复调用会返回空字符串
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











