【问题标题】:Unable to create a torrent's info hash无法创建 torrent 的信息哈希
【发布时间】:2020-04-09 17:52:39
【问题描述】:

我无法找到如何为 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 可以在 here (Ubuntu 14.04 Desktop amd64) 找到。

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

【问题讨论】:

    标签: java hash bittorrent torrent info-hash


    【解决方案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);
    

    【讨论】:

    • 感谢您的所有好评!我故意删除最后一个字节以说明文件的编码(更具体地说,最后发现的附加“e”)。但是,这仍然无法获得我正在寻找的输出。
    • 此代码将使用 SHA-1 准确地散列精确字节(从 4:info 之后的第一个字节到文件的最后一个字节);我不熟悉 .torrent 文件格式,可能会仔细检查您是否使用正确的算法对正确的内容进行哈希处理。
    • 我发现我使用了错误的哈希值来进行比较:) 感谢所有的帮助,我最终采用了不同的实现,但在测试你的代码时它产生了相同的结果。
    【解决方案2】:

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

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

    相反,您应该使用正确的编码解码器-编码器to extract the info dict and then re-encode it for hashing 或标记器来查找覆盖信息值的确切字节范围。请注意,您 need a validating parser 前者,而后者也可以处理一些不合规格的边缘情况。除非您想自己实现它们,否则您可能希望找到一个可以为您处理这些的库。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-20
      • 1970-01-01
      • 1970-01-01
      • 2013-04-11
      • 2017-07-26
      • 2019-11-10
      相关资源
      最近更新 更多