十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP实现BT种子转磁力链接:bencode解析与SHA1哈希实践

PHP实现BT种子转磁力链接:bencode解析与SHA1哈希实践 简介一套基于PHP实现的BT种子转磁力链接工具代码包面向PHP开发者、P2P下载工具爱好者及需要研究DHT协议与种子解析的读者。资源包含完整可运行的PHP脚本核心功能是读取.torrent种子文件解析其中info部分并提取Info Hash再结合文件名与Tracker地址生成标准格式的magnet链接帮助用户跳过种子文件直接发起P2P下载。包体共5个文件包括4个PHP源码文件与1个Markdown说明文档。其中BEncode.php与BDecode.php负责B编码数据的编码与解码是解析种子文件的关键模块index.php与demo.php演示了完整调用流程README.md对转换原理、函数用法和注意事项做了说明。整个压缩包仅3KB结构精简适合快速阅读和二次开发。已有4065人学习下载。通过这份资源读者可以掌握种子文件解析、Info Hash计算、磁力链接构造及Tracker参数处理等核心方法同时了解PHP在轻量级工具脚本中的应用技巧可直接用于搭建私有转换服务或嵌入现有下载系统。 做这个项目的起因特别日常我在局域网里给朋友分享一部纪录片文件好几个GB直接传文件太慢于是想把本地的BT种子转成磁力链接一串字符串发过去就能让对方自己下载。结果搜遍网上要么是现成的在线工具要么是别人封装好的接口自己想离线处理、批量转换居然没有一个趁手的PHP方案。那就自己写一个吧核心目标就一条上传.torrent文件PHP服务端解析出info字段计算SHA1哈希拼成标准magnet链接整个过程不依赖任何外部API。这篇文章会把整个实现思路、bencode解析原理、PHP代码细节和踩过的坑完整整理出来适合有一定PHP基础、想了解BT协议底层结构的开发者也适合需要一个“种子转磁力”离线工具的运维或站长参考。1. 项目整体设计与核心思路拆解1.1 磁力链接和BT种子到底差在哪先明确一个概念种子里装的和磁力链接里携带的核心都是同一个东西——文件的哈希指纹。BT种子是一个按照bencode规范编码的文件里面记录了文件名、大小、分块长度和每个分块的SHA1哈希值。磁力链接则是把这堆信息精简成一行字符串格式是magnet:?xturn:btih:40位十六进制或32位Base32编码的信息哈希dn显示名称其中xturn:btih后面的值就是从种子文件的info字典原始字节计算出的SHA1哈希再经过编码得到的。也就是说转换过程不需要重新下载任何数据只读取种子本身就能完成。这也是为什么这个工具可以做成纯本地、纯离线运行。1.2 转换的核心info字典与SHA1哈希整个项目最关键的认知是SHA1的对象不是整个.torrent文件而是其中info字段对应的那一段编码字节。torrent顶层是一个字典包含announceTracker地址、info文件描述等键。计算哈希时必须取出info键值的原始字节序列做SHA1因为bencode字典重编码时键序可能被打乱导致哈希结果不一致。我之前第一版实现踩过这个坑把整个种子解码成PHP数组再重新encode成bencode字符串去算哈希结果生成的磁力链接和原种子对不上下载器完全不认。后来翻libtorrent源码才明白info_hash的要求就是“原始字节段原封不动地做SHA1”。所以正确做法是在解码过程中记录info值的起止偏移量直接从原始文件内容中截取那一段字节。2. 关键技术拆解Bencode编解码2.1 Bencode的四种数据类型与解码实现bencode总共只有四种类型字节字符串、整数、列表、字典。解析规则非常简洁字节字符串十进制长度:原始字节整数i十进制整数e列表l任意bencode值...e字典d键值...e键是字节字符串键值必须有序我们需要一个递归下降解析器因为列表和字典内部可以嵌套任意层级而BT种子的info部分就是一组嵌套结构。PHP的字符串函数是二进制安全的所以直接用substr、ord、strpos处理原始字节没有问题不需要做任何编码转换。解码器的核心实现如下function bdecode(string $data, int $pos 0) { $c $data[$pos]; if ($c i) { $pos; $end strpos($data, e, $pos); $num (int)substr($data, $pos, $end - $pos); $pos $end 1; return $num; } if ($c l) { $pos; $items []; while ($data[$pos] ! e) { $items[] bdecode($data, $pos); } $pos; return $items; } if ($c d) { $pos; $dict []; while ($data[$pos] ! e) { $key bdecode($data, $pos); $dict[$key] bdecode($data, $pos); } $pos; return $dict; } // 默认是字节字符串 $colon strpos($data, :, $pos); $len (int)substr($data, $pos, $colon - $pos); $pos $colon 1; $str substr($data, $pos, $len); $pos $len; return $str; }核心逻辑是共享一个位置指针$pos每次解析完一个值就更新指针这样在字典里解析完键之后紧接着就能解析对应的值指针酸准地在原始数据上移动。2.2 提取原始info字段别踩“重编码”的坑正如前面说的生成info_hash必须使用info键对应的原始bencode字节。方法是在遍历顶层字典时逐个解码键名当发现键名等于info时记录当前位置$pos作为起始偏移递归调用bdecode解析该值解析完成后$pos指向info值的结束位置截取这一段原始字节即可function extract_info_raw(string $torrentData): array { $pos 0; $str $torrentData[$pos]; if ($str ! d) { throw new RuntimeException(不是合法的种子文件); } $pos; while ($torrentData[$pos] ! e) { $keyStart $pos; $colon strpos($torrentData, :, $pos); $len (int)substr($torrentData, $pos, $colon - $pos); $pos $colon 1; $key substr($torrentData, $pos, $len); $pos $len; if ($key info) { $infoStart $pos; $info bdecode($torrentData, $pos); $infoRaw substr($torrentData, $infoStart, $pos - $infoStart); return [$infoRaw, $info]; } // 跳过非info键的值 bdecode($torrentData, $pos); } throw new RuntimeException(种子里缺少info字段); }这一步是整个工具准确性的核心保障。有人会问直接用解码后的数组重新bencode不也一样吗不一样。PHP数组按插入顺序输出键值但如果种子里字典键顺序和原文件不一致比如原文件先写了name再写piece length而你重编码时字典顺序变了得到的SHA1就变了。BT规范的字典键虽然要求有序但实际网络上有大量种子并不严格遵守所以“原始字节截取”是兼容性最好的方案。3. 实操环节完整实现与代码解读3.1 环境准备与项目结构项目没有任何第三方依赖PHP 7.4及以上都能跑虚拟主机、宝塔面板、Docker容器都可以直接部署。文件结构就四个bencode2magnet/ ├── index.php # Web入口上传表单 结果展示 ├── lib/Bencode.php # 解码器与info提取 ├── lib/Magnet.php # 磁力链接生成器 └── uploads/ # 临时目录注意配置写权限为什么不依赖现成的bencode扩展因为PHP官方没有内置bencode解析函数PECL的bencode扩展又多年不更新在PHP 8下能否编译是个问题。纯PHP实现最大的好处是零依赖、随处跑几百行代码就能搞定后续想加批量处理、命令行模式也方便。3.2 核心解码类与磁力链接生成代码磁力链接生成器需要做两件事计算SHA1哈希以及对哈希做Base32编码。PHP的hash(sha1, $raw)返回的是十六进制字符串直接用当然也行但考虑到兼容性标准BT磁力链接普遍使用Base32编码的20字节二进制哈希也就是40个字符里只用大写字母A-Z和数字2-7。我手写了一个简单的Base32编码函数核心是按5位一组切分比特流function base32_encode_btih(string $raw): string { $chars ABCDEFGHIJKLMNOPQRSTUVWXYZ234567; $result ; $buffer 0; $bitsLeft 0; for ($i 0, $len strlen($raw); $i $len; $i) { $buffer ($buffer 8) | ord($raw[$i]); $bitsLeft 8; while ($bitsLeft 5) { $index ($buffer ($bitsLeft - 5)) 31; $result . $chars[$index]; $bitsLeft - 5; } } // 160位正好整除5理论无遗留不做尾端补位 return $result; } function generateMagnet(string $torrentData): string { [$infoRaw, $info] extract_info_raw($torrentData); $hashBin sha1($infoRaw, true); // 20字节二进制 $btih base32_encode_btih($hashBin); // 标准32字符btih $name rawurlencode($info[name] ?? file); return magnet:?xturn:btih:$btihdn$name; }sha1($infoRaw, true)的第二个参数true很关键它让函数返回20字节的二进制字符串而不是40个字符的十六进制字符串Base32编码函数需要的是二进制输入。Base32编码的原理可以类比成把80个比特位重新切成一堆5比特的段每段对应一个字符160位哈希正好生成32个字符不需要补位这给了我们一个天然的自检条件——如果生成的btih不是32位说明代码写错了。3.3 Web上传交互与安全限制Web入口的交互逻辑很直接上传种子文件后端校验调用generateMagnet页面展示生成的磁力链接和二维码如果有需要。但有几个安全细节必须处理限制上传大小PHP默认upload_max_filesize是2M种子文件一般很小但有些大种子的piece列表可能超过2M需要适当调大。校验真实MIME类型不能用客户端传来的Content-Type因为那个值可以随意伪造最靠谱的判断是直接尝试解码首字节不是d就拒绝。用完即删把临时上传文件移动到uploads/目录处理完立即unlink避免服务器堆积垃圾文件。表单部分不复杂关键是正确配置PHP上传相关的三个参数file_uploads总开关、upload_max_filesize单文件大小、post_max_sizePOST总大小三者关系类似水桶效应post_max_size必须大于upload_max_filesize否则文件稍大一点就会上传失败且不报明确错误。4. 踩坑记录与问题排查速查表4.1 常见的运行时错误与配置问题这个项目上线后我陆续处理过几类典型问题整理成速查表基本覆盖了大多数人会遇到的情况症状可能原因解决办法上传文件后GET不到$_FILESpost_max_size小于上传体积调大post_max_size并重启PHP-FPM提示UPLOAD_ERR_INI_SIZEupload_max_filesize限制在php.ini调大该值注意要大于种子文件实际大小生成链接无法下载btih被错误截取或编码用strlen确认btih是32字符再用已知种子对比官方hash中文文件名乱码name未做URL编码用rawurlencode而非urlencode解析报“不是合法种子文件”文件开头的d被破坏检查是否通过HTTP文本模式传输导致二进制数据损坏页面白屏无输出PHP语法错误或扩展缺失开启display_errors查看错误日志另一个值得提醒的是track_errors配置。如果部署环境是老版本PHP配置改过来的升级到PHP 8后可能会遇到类似FATAL ERROR: directive track_errors is no longer available in PHP的报错这是PHP 8移除了该指令导致的需要在php.ini里删除对应配置项。这类问题和项目本身的编码无关但在宝塔面板等一键部署环境里特别常见。4.2 大文件、边界情况与兼容性处理我测试过几类特殊种子踩过的边界条件大致有这些单文件种子和多文件种子单文件种子的info里是length字段多文件种子则是files数组每个元素包含path数组和length。提取info哈希的代码不受影响但展示文件名时需要走不同的分支。超大piece列表的种子比如几十GB的蓝光原盘piece数量可能上万对应pieces字符串是每个20字节的SHA1哈希拼接。解码时substr直接截取没问题但输出时如果打印到页面要注意这部分二进制可能包含非法UTF-8字符所以pieces字段只在调试时用bin2hex查看。带有private标志的私有种子这类种子显式要求禁用DHT转成磁力链接后可能只能在部分下载器里靠Tracker工作属于正常现象不是代码问题。文件开头带BOM或空白标准bencode要求首字符必须是d但我遇到过一些手动拼接的种子文件前面混入了几个空格解析会直接失败。一个稳妥的做法是先用ltrim($data, \t\n\r\0\x0B)清理前导空白但要注意只清理开头不要在中间任何位置做裁剪。调试阶段最重要的参照物是已知哈希的种子。随便找一个大版本Linux系统的官方torrent文件官方网站通常公布文件的信息哈希把生成的磁力链接和官网对比完全一致就说明工具没问题。我在开发时把这一步做成了自动化测试脚本避免后续改代码时回归。5. 扩展思路更贴合实际使用的改进方向基础版本够用了但真正放到服务器上长期跑还会有几个值得升级的方向。第一个是命令行模式。Web上传适合偶尔用一两回但如果你要批量处理几百个种子文件用Web界面显然不现实。可以用一个简单的CLI入口把第一个参数当作种子文件路径循环读取、转换、输出文件名\t磁力链接配合xargs就能实现批量转换find /data/torrents -name *.torrent -exec php cli.php {} \; magnets.txt第二个是集成下载器的远程调用接口。很多下载器都提供JSON-RPC或HTTP接口比如qBittorrent WebUI的/api/v2/torrents/add接口支持直接添加磁力链接。转换完之后把magnet链接交给这个接口一步到位地完成“收到种子文件→转磁力→自动添加下载任务”的闭环。第三个是增加缓存机制。同一份种子被反复上传时每次都要重新解析、计算虽然性能损耗不大但加上一个基于文件SHA1的缓存表能让响应更快也方便统计使用情况。这个看实际使用频率决定要不要做。做一个“种子转磁力”工具本质上是把BT协议最底层的数据结构摸了一遍。bencode这个格式看起来简陋但正是这种极简设计让整个PT/BT生态能够长盛不衰。如果你日常接触大量种子文件这套解析逻辑还可以延伸去读取种子里的文件列表、做资源分类或者二次整理思路都是相通的。遇到问题的时候别急着看别人代码先去翻协议规范绝大多数答案都在原版规范里写得很清楚。本文还有配套的精品资源点击获取
返回列表