0

我无法找到如何为 torrent 文件生成相应信息哈希的问题。这是我到目前为止的代码:

InputStream input = null;
try {
    MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
    input = new FileInputStream(file);
    StringBuilder builder = new StringBuilder();
    while (!builder.toString().endsWith("4:info")) {
       builder.append((char) input.read()); // It's ASCII anyway.
    }
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    for (int data; (data = input.read()) > -1; output.write(data));
    sha1.update(output.toByteArray(), 0, output.size() - 1);
    this.infoHash = sha1.digest();
    System.out.println(new String(Hex.encodeHex(infoHash)));
} catch (NoSuchAlgorithmException | IOException e) {
     e.printStackTrace();
} finally {
    if (input != null) try { input.close(); } catch (IOException ignore) {}
}

以下是我的预期和实际哈希:

Expected: d4d44272ee5f5bf887a9c85ad09ae957bc55f89d
Actual: 4d753474429d817b80ff9e0c441ca660ec5d2450

我试图为其生成信息哈希的 torrent 可以在此处找到 (Ubuntu 14.04 Desktop amd64)

如果我可以提供更多信息,请告诉我,谢谢!

4

2 回答 2

1

异常包含 4 个有用的信息位:类型、消息、跟踪和原因。您已经丢弃了 4 条相关信息中的 3 条。此外,代码是流程的一部分,当发生错误时,通常该流程根本无法完成。然而,在例外情况下,您的流程仍在继续。停止这样做;你写的代码只会伤害你。删除尝试和捕获。throws在您的方法签名上添加一个子句。如果您不能,则首选默认值(如果生成此代码来执行此操作,请更新您的 IDE)是throw new RuntimeException("Unhandled", e);. 这更短,不会破坏 4 个有趣的信息位中的任何一个,并结束一个进程。

另外,处理输入流close方法的正确方法IOException是:忽略它的概念也是错误的。抛出的可能性很小,但如果是,您应该假设您没有读取每个字节。因为这是对不匹配哈希的一种解释,所以它被误导了。

最后,使用正确的语言结构:这里有一个 try-with-resources 语句会更好地工作。

你用output.size() - 1;调用更新 除非您想故意忽略最后一个字节,否则这是一个错误;您正在删除读取的最后一个字节。

将字节读入构建器,然后按字节将构建器转换为字符串,然后检查最后一个字符的效率非常低;对于一个小到 1MB 的文件,这会引起相当大的麻烦。

从原始数据中一次读取一个字节FileInputStream也是这种低效水平,因为每次读取都会导致文件访问(读取 1 个字节与读取整个缓冲区一样昂贵,因此,它比需要的速度慢大约 50000 倍)。

下面是如何使用更新的 API 来做到这一点,看看这段代码读起来有多好。它在错误条件下也表现得更好:

byte[] data = Files.readAllBytes(Paths.get(fileName));
var search = "4:info".getBytes(StandardCharsets.US_ASCII);
int searchIdx = -1;
for (int i = 0; searchIdx == -1 && i < data.length - search.length; i++) {
    for (int j = 0; j < search.length; j++) {
        if (data[i + j] != search[j]) break;
        if (j == search.length - 1) searchIdx = i + j;
    }
}
if (searchIdx == -1) throw new IOException("Input torrent file does not contain marker");

var sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(data, searchIdx, data.length - searchIdx);
byte[] hash = sha1.digest();
StringBuilder hex = new StringBuilder();
for (byte h : hash) hex.append(String.format("%02x", h));
System.out.println(hex);
于 2019-12-17T10:49:20.067 回答
1

虽然rzwitserloot 的答案涵盖了一些通用的 java 编码实践,但在 bittorrent 级别上也存在正确性问题。

您正在对结构化数据格式使用字符串处理,这与尝试使用 regex 解析 html的错误几乎相同。在这种情况下,您假设数据可以包含字符串的唯一位置是4:infoinfo dict 的顶级字典键,并且 info 字典是顶级字典的最后一个条目。

相反,您应该使用正确的编码解码器 - 编码器来提取信息字典,然后重新编码它以进行散列或标记器以找到覆盖信息值的确切字节范围。请注意,您需要对前者进行验证解析器,而后者也可以处理一些超出规范的边缘情况。除非您想自己实现它们,否则您可能希望找到一个为您处理这些的库。

此外,您假设数据是 ASCII。bencoding 实际上是一种二进制格式,在某些地方只是倾向于使用 ascii。您应该直接对字节数组进行操作。您的输入已经是二进制的,散列器需要二进制,因此遍历字符串非常迂回。

于 2019-12-20T01:10:33.953 回答