ThinkPHP上传后须用$file->getRealPath()调用hash_file('sha256')计算服务端Hash,与user_id等存入attachment表并索引;禁用客户端Hash校验;定期用命令行脚本扫描存量文件完整性。

ThinkPHP上传后如何立即计算文件Hash值
上传完成不代表文件可信,必须在服务端读取临时文件并生成校验值。ThinkPHP的 think\File 对象提供了原始路径,但不能直接对 $_FILES 数组或表单字段名做哈希——它只是元信息。
关键点:必须用临时文件物理路径($file->getRealPath())参与计算,而非表单提交的文件名或内容流。
- 使用
hash_file('sha256', $file->getRealPath())最稳妥,支持大文件且不占内存 - 避免用
file_get_contents()+hash(),超 2MB 可能触发内存限制或超时 - 若需兼容 PHP 7.2 以下,改用
md5_file()或sha1_file(),但安全性弱于 SHA-256
如何把Hash值和上传记录绑定存储
Hash不是摆设,必须和业务数据强关联。常见错误是只存 Hash 却没存对应文件 ID、用户 ID 或时间戳,导致后期无法追溯校验上下文。
推荐在模型层统一处理:上传成功后,将 hash、original_name、size、user_id 一起写入附件表(如 attachment),而不是塞进 JSON 字段或单独配置文件。
立即学习“PHP免费学习笔记(深入)”;
- 数据库字段建议设为
CHAR(64)(SHA-256)或CHAR(32)(MD5),加索引便于查重 - 插入前用
Db::table('attachment')->where('hash', $hash)->find()检查是否已存在,可防重复上传(非必须,但省带宽) - 不要把 Hash 存在 session 或前端 hidden input 里——用户可篡改,完全不可信
上传时校验客户端传来的Hash是否匹配(慎用)
有些方案让前端先算好 Hash 再随表单提交,服务端比对。这看似“提前拦截”,实则风险极高:用户可伪造任意 Hash 值,且 JS 计算大文件易卡死或精度丢失(如 Blob.slice 精度问题)。
仅在可信内网环境 + 前端 SDK 强管控下才考虑此方式;生产环境默认应忽略客户端传入的 file_hash 字段,以服务端重算为准。
- 若仍要校验,必须开启
check_hash => true并在控制器中显式对比:if ($clientHash !== hash_file('sha256', $file->getRealPath())) { throw new Exception('Hash mismatch'); } - 注意:IE 或旧版 WebView 可能不支持
FileReader.readAsArrayBuffer,前端算 Hash 本身就不稳定 - 更安全的做法是上传后异步触发校验任务(如队列),避免阻塞主流程
上线后如何批量验证存量文件完整性
Hash 校验不是一锤子买卖。服务器迁移、磁盘故障、甚至误删重传都可能导致文件内容变化。必须有机制定期抽检或全量扫描。
写个简单命令行脚本即可:php think repair:hash --path=public/uploads/ --ext=pdf,docx,遍历目录比对数据库中记录的 Hash 值。
- 扫描时跳过正在被写入的文件(检查
is_writable()或用flock()避免读到半截内容) - 发现不一致时,记录日志并标记状态为
corrupted,禁止前端访问该记录,而非直接删除 - 别依赖文件修改时间(
filemtime)判断是否变更——它可能被 touch 重置,且不反映内容真实差异
最易被忽略的是:Hash 值本身没做防篡改保护。如果攻击者能写数据库,他就能同步改掉 Hash 字段。所以关键文件建议额外签名(如用私钥对 file_id . hash 做 HMAC-SHA256),校验时一并验证签名有效性。



















