无法创建 Torrent 的信息哈希

Unable to create a torrent's info hash

我无法找到有关如何为 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)

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

异常包含 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);

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

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

相反,您应该为前者使用适当的 bencoding 解码器-编码器 to extract the info dict and then re-encode it for hashing or a tokenizer to find the exact byte-range covering the info value. Note that you need a validating parser,而后者也可以处理一些超出规格的边缘情况。除非您想自己实现它们,否则您可能需要找到一个可以为您处理此问题的库。

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