如何解码BitTorrent v2种子文件info元数据的二进制pieces root?
BitTorrent v2 提取pieces root哈希的实现方案(PHP/C)
核心背景回顾
BitTorrent v2的种子文件采用Bencode编码,info元数据中的pieces root是二进制形式的SHA256默克尔根,由16KiB块构建二叉默克尔树(末尾块不足16KiB时补零)。我们需要把这个二进制哈希转成十六进制字符串,用于网页展示。
PHP实现方案(新手友好)
步骤1:解析Bencode格式的种子文件
BT种子是Bencode编码的键值对结构,必须先解析才能拿到info里的pieces root。下面是一个简化版的Bencode解析函数(仅处理我们需要的字典、字符串类型):
function bdecode($data) { $pos = 0; $len = strlen($data); $decode = function() use (&$data, &$pos, &$len, &$decode) { if ($pos >= $len) return null; $c = $data[$pos]; // 处理字符串:格式为 "数字:内容",例 "10:helloworld" if (ctype_digit($c)) { $colon_pos = strpos($data, ':', $pos); $str_len = (int)substr($data, $pos, $colon_pos - $pos); $pos = $colon_pos + 1; $str = substr($data, $pos, $str_len); $pos += $str_len; return $str; } // 处理字典:格式为 "d键值对e" elseif ($c === 'd') { $pos++; $dict = []; while ($data[$pos] !== 'e') { $key = $decode(); $value = $decode(); $dict[$key] = $value; } $pos++; return $dict; } return null; }; return $decode(); }
步骤2:读取种子并提取pieces root
// 1. 读取种子文件内容 $torrent_content = file_get_contents('your_torrent_file.torrent'); if (!$torrent_content) { die('无法读取种子文件'); } // 2. 解析Bencode数据 $torrent_data = bdecode($torrent_content); if (!isset($torrent_data['info']['pieces root'])) { die('该种子不是BitTorrent v2格式,无pieces root字段'); } // 3. 提取二进制哈希并转十六进制 $pieces_root_bin = $torrent_data['info']['pieces root']; // 用bin2hex直接转十六进制,比unpack更简单 $pieces_root_hex = bin2hex($pieces_root_bin); // 输出结果(用于网页展示) echo 'Pieces Root哈希:' . $pieces_root_hex;
关于unpack失败的原因
如果一定要用unpack,正确写法是:
$pieces_root_hex = implode('', unpack('H*', $pieces_root_bin));
unpack('H*', $bin)会返回一个数组,每个元素是十六进制片段,必须用implode拼接成完整字符串,之前大概率是没做拼接导致失败。
C语言实现方案(新手友好)
步骤1:核心思路
C语言需要手动解析Bencode,读取种子文件后定位info字典里的pieces root字段,再将二进制哈希转成十六进制字符串。
步骤2:完整代码实现(带注释)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> // 读取整个文件到内存 char* read_file(const char* filename, size_t* file_len) { FILE* fp = fopen(filename, "rb"); if (!fp) { perror("无法打开种子文件"); return NULL; } // 获取文件大小 fseek(fp, 0, SEEK_END); *file_len = ftell(fp); fseek(fp, 0, SEEK_SET); // 分配内存并读取 char* buffer = malloc(*file_len + 1); if (!buffer) { perror("内存分配失败"); fclose(fp); return NULL; } fread(buffer, 1, *file_len, fp); fclose(fp); buffer[*file_len] = '\0'; return buffer; } // 解析Bencode字符串(返回内容,更新pos到字符串结束位置) char* bdecode_string(const char* data, size_t* pos) { // 读取字符串长度数字 size_t len = 0; while (isdigit(data[*pos])) { len = len * 10 + (data[*pos] - '0'); (*pos)++; } if (data[*pos] != ':') return NULL; // 格式错误 (*pos)++; // 跳过冒号 // 分配内存存储字符串 char* str = malloc(len + 1); if (!str) return NULL; memcpy(str, data + *pos, len); str[len] = '\0'; (*pos) += len; return str; } // 解析Bencode字典,找到指定key的value void bdecode_dict_find(const char* data, size_t* pos, const char* target_key, char** out_value, size_t* out_len) { if (data[*pos] != 'd') return; // 不是字典 (*pos)++; // 跳过'd' while (data[*pos] != 'e') { // 解析key char* key = bdecode_string(data, pos); if (!key) break; // 解析value(只处理字符串类型) char* value = NULL; size_t value_len = 0; if (isdigit(data[*pos])) { value = bdecode_string(data, pos); value_len = strlen(value); } else { // 跳过非字符串类型的value while (data[*pos] != 'e' && !isdigit(data[*pos])) { (*pos)++; } } // 匹配目标key if (strcmp(key, target_key) == 0) { *out_value = value; *out_len = value_len; free(key); return; } free(key); if (value) free(value); } (*pos)++; // 跳过'e' } // 二进制转十六进制字符串 char* bin_to_hex(const unsigned char* bin, size_t bin_len) { char* hex = malloc(bin_len * 2 + 1); if (!hex) return NULL; const char* hex_chars = "0123456789abcdef"; for (size_t i = 0; i < bin_len; i++) { hex[i*2] = hex_chars[(bin[i] >> 4) & 0x0F]; hex[i*2+1] = hex_chars[bin[i] & 0x0F]; } hex[bin_len*2] = '\0'; return hex; } int main() { const char* torrent_path = "your_torrent_file.torrent"; size_t file_len; char* torrent_data = read_file(torrent_path, &file_len); if (!torrent_data) return 1; size_t pos = 0; // 先定位info字典 char* info_data = NULL; size_t info_len = 0; bdecode_dict_find(torrent_data, &pos, "info", &info_data, &info_len); if (!info_data) { printf("种子文件格式错误,找不到info字段\n"); free(torrent_data); return 1; } // 在info字典里找pieces root pos = 0; char* pieces_root_bin = NULL; size_t pieces_root_len = 0; bdecode_dict_find(info_data, &pos, "pieces root", &pieces_root_bin, &pieces_root_len); if (!pieces_root_bin) { printf("该种子不是BitTorrent v2格式,无pieces root字段\n"); free(info_data); free(torrent_data); return 1; } // 转十六进制并输出 char* pieces_root_hex = bin_to_hex((unsigned char*)pieces_root_bin, pieces_root_len); if (pieces_root_hex) { printf("Pieces Root哈希:%s\n", pieces_root_hex); free(pieces_root_hex); } // 释放内存 free(pieces_root_bin); free(info_data); free(torrent_data); return 0; }
编译与运行
- 将代码保存为
bt_v2_extract.c - 用GCC编译:
gcc bt_v2_extract.c -o bt_v2_extract - 运行:
./bt_v2_extract(Windows下为bt_v2_extract.exe),记得替换代码中的种子文件路径。
内容的提问来源于stack exchange,提问作者greenandgreen
相关产品推荐
相关产品推荐

