Java的摘要和外部工具的结果不同

195

我编写了一个简单的Java类来生成Windows计算器文件的哈希值。我使用的是Windows 7 Professional with SP1操作系统,尝试过Java 6.0.29Java 7.0.03。有人能告诉我为什么Java与(许多!)外部实用程序和/或网站生成的哈希值不同吗?所有外部工具相互匹配,只有Java返回不同的结果。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.CRC32;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Checksum 
{
    private static int size = 65536;
    private static File calc = new File("C:/Windows/system32/calc.exe");

    /*
        C:\Windows\System32\calc.exe (verified via several different utilities)
        ----------------------------
        CRC-32b = 8D8F5F8E
        MD5     = 60B7C0FEAD45F2066E5B805A91F4F0FC
        SHA-1   = 9018A7D6CDBE859A430E8794E73381F77C840BE0
        SHA-256 = 80C10EE5F21F92F89CBC293A59D2FD4C01C7958AACAD15642558DB700943FA22
        SHA-384 = 551186C804C17B4CCDA07FD5FE83A32B48B4D173DAC3262F16489029894FC008A501B50AB9B53158B429031B043043D2
        SHA-512 = 68B9F9C00FC64DF946684CE81A72A2624F0FC07E07C0C8B3DB2FAE8C9C0415BD1B4A03AD7FFA96985AF0CC5E0410F6C5E29A30200EFFF21AB4B01369A3C59B58


        Results from this class
        -----------------------
        CRC-32  = 967E5DDE
        MD5     = 10E4A1D2132CCB5C6759F038CDB6F3C9
        SHA-1   = 42D36EEB2140441B48287B7CD30B38105986D68F
        SHA-256 = C6A91CBA00BF87CDB064C49ADAAC82255CBEC6FDD48FD21F9B3B96ABF019916B    
    */    

    public static void main(String[] args)throws Exception {
        Map<String, String> hashes = getFileHash(calc);
        for (Map.Entry<String, String> entry : hashes.entrySet()) {
            System.out.println(String.format("%-7s = %s", entry.getKey(), entry.getValue()));
        }
    }

    private static Map<String, String> getFileHash(File file) throws NoSuchAlgorithmException, IOException {
        Map<String, String> results = new LinkedHashMap<String, String>();

        if (file != null && file.exists()) {
            CRC32 crc32 = new CRC32();
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
            MessageDigest sha256 = MessageDigest.getInstance("SHA-256");

            FileInputStream fis = new FileInputStream(file);
            byte data[] = new byte[size];
            int len = 0;
            while ((len = fis.read(data)) != -1) {
                crc32.update(data, 0, len);
                md5.update(data, 0, len);
                sha1.update(data, 0, len);
                sha256.update(data, 0, len);
            }
            fis.close();

            results.put("CRC-32", toHex(crc32.getValue()));
            results.put(md5.getAlgorithm(), toHex(md5.digest()));
            results.put(sha1.getAlgorithm(), toHex(sha1.digest()));
            results.put(sha256.getAlgorithm(), toHex(sha256.digest()));
        }
        return results;
    }

    private static String toHex(byte[] bytes) {
        String result = "";
        if (bytes != null) {
            StringBuilder sb = new StringBuilder(bytes.length * 2);
            for (byte element : bytes) {
                if ((element & 0xff) < 0x10) {
                    sb.append("0");
                }
                sb.append(Long.toString(element & 0xff, 16));
            }
            result = sb.toString().toUpperCase();
        }
        return result;
    }

    private static String toHex(long value) {
        return Long.toHexString(value).toUpperCase();
    }

}

64
除了计算校验和外,同时将文件复制到某个临时文件中,这样您就可以比较Java得出的结果与使用其他工具得出的结果是否相同。Windows可能会有一些奇怪的问题...我从未见过Java在计算哈希时出错。 - Pawel Veselov
3
所有的程序员都应该像这样编写程序!这段代码非常整洁简洁。 - Martijn Courteaux
2
@user567496:就代码而言,它与其他Java SHA-1实现以及命令行sha1sum工具相比,可以正确生成SHA-1哈希值。(在Linux上测试过文件,但未测试calc.exe) - TacticalCoder
@TacticalCoder 很抱歉没有交代清楚,我想指出的是由于对输入流的错误解释(在帖子的情况下,因为它缺少适当的字符集),MessageDigest并不总是会给出正确的值。在这种情况下,正如其他人指出的那样,可能是因为文件正在使用中。(总之,MessageDigest 计算错误摘要的可能性非常小)。 - Federico Vera
1
@Fido:在这种情况下,它不可能是字符集问题,因为OP正在读取原始字节:他没有解码字符。 - TacticalCoder
显示剩余7条评论
1个回答

240

明白了,Windows文件系统的行为取决于进程的架构不同而有所不同。这篇文章详细解释了这一点:

但是如果在64位Windows上运行硬编码系统路径并且运行32位应用程序怎么办?你可能会想,它们如何找到新的SysWOW64文件夹,而无需更改程序代码。答案是模拟器会将对System32文件夹的调用重定向到SysWOW64文件夹,因此即使将文件夹硬编码到System32文件夹(例如 C:\Windows\System32),模拟器也会确保使用SysWOW64文件夹。因此,可以将使用System32文件夹的相同源代码编译为32位和64位程序代码而不需要做任何更改。

尝试将calc.exe复制到其他地方...然后再次运行相同的工具。您将得到与Java相同的结果。Windows文件系统给工具提供的数据与给Java提供的数据有些不同...我确信这与它位于Windows目录中有关,因此可能被“不同地”处理。

此外,我已在C#中重现了此问题...并发现它取决于你正在运行的进程的架构。因此,这是一个示例程序:

using System;
using System.IO;
using System.Security.Cryptography;

class Test
{
    static void Main()
    {
        using (var md5 = MD5.Create())
        {
            string path = "c:/Windows/System32/Calc.exe";
            var bytes = md5.ComputeHash(File.ReadAllBytes(path));
            Console.WriteLine(BitConverter.ToString(bytes));
        }
    }
}

以下是控制台会话记录(不包括编译器的冗杂信息):

c:\users\jon\Test>csc /platform:x86 Test.cs    

c:\users\jon\Test>test
60-B7-C0-FE-AD-45-F2-06-6E-5B-80-5A-91-F4-F0-FC

c:\users\jon\Test>csc /platform:x64 Test.cs

c:\users\jon\Test>test
10-E4-A1-D2-13-2C-CB-5C-67-59-F0-38-CD-B6-F3-C9

64
calc.exe 有两个版本:64位在 C:\Windows\system32\,32位在 C:\Windows\SysWOW64\。为了在32位进程中兼容,C:\Windows\system32\ 被映射到 C:\Windows\SysWOW64\上。64位进程将启动64位 calc,32位进程则启动32位 calc。不出所料,它们的校验和是不同的。如果你持续打开文件并使用 handles.exe 或 Process Explorer 查看,你会看到不同的路径。 - Richard
25
那个东西被称为文件系统重定向器(File System Redirector)。 - David Heffernan
9
意见不一,或许与“可行性”的定义有关。所有这些虚拟化都违反了最小惊讶原则,并增加了成本(分配和运行时)。其他操作系统通过更少的问题/泄漏抽象实现了更好的32位在64位支持和更好的应用程序虚拟化(尝试在Wow64上运行垃圾回收程序,或者尝试比较md5摘要等细分领域案例)。 - sehe
5
有时候我会想人们是否会点赞你仅仅是因为你是Jon Skeet,而不是仅仅因为答案。我并不是说答案不好或其他什么,但是当你的答案只是“Windows中正在发生某些事情”(公平地说,你确实提供了一个链接,但还是如此),获得145个赞似乎人们在点赞时考虑的不仅仅是你的答案。我不是在攻击你,但这意味着我需要一段时间才能赶上你 :P - Jason Ridge
5
这篇博客是我找到的。我原本期望能看到一些Jon Skeet式的神奇操作,但感觉好像自己也能做到。虽然可能没有他那么快,但总之就是这样。好吧,或许我还是做不到,但是无所谓了。至于里程碑的问题,这并不能给我太多安慰,因为这意味着你每天都会达到它,而我永远也赶不上你。嗯,算了吧... - Jason Ridge
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接