Различные результаты с дайджестом Java по сравнению с внешними утилитами

194

Я написал простой Java-класс для генерации хеш-значений файла Windows Calculator. Я использую Windows 7 Professional with SP1. Я пытался Java 6.0.29и Java 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();
    }

}
Майк Вьенс
источник
Я думаю, что ваш toHex не так. Если вы это сделаете int newElement = ((int) element) & 0xffи используете вместо этого, это решит вашу проблему?
Запл
64
Параллельно с вычислением контрольной суммы скопируйте файл в какой-нибудь временный файл, чтобы вы могли сравнить, что получает Java с тем, что вы получаете, когда используете другие инструменты. Окна могут быть такими странными ... Я никогда не видел, чтобы Java допустила ошибку при вычислении хешей ...
Павел Веселов
3
Все программисты должны программировать так! Код очень чистый и аккуратный.
Мартин Курто
2
@ user567496: за то, что он стоит, ваш код дает правильные хэши SHA-1 по сравнению с другими реализациями Java SHA-1 и по сравнению с утилитой командной строки sha1sum ... (протестировано с файлами в Linux, а не с calc.exe)
TacticalCoder
1
@Fido: в этом случае это не могло быть проблемой кодировки, потому что OP читает необработанные байты: он не декодирует символы.
TacticalCoder

Ответы:

239

Понял. Файловая система Windows ведет себя по-разному в зависимости от архитектуры вашего процесса. Эта статья объясняет все это, в частности:

Но как быть с 32-битными приложениями, которые имеют жестко запрограммированный системный путь и работают в 64-битной Windows? Вы можете подумать, как они могут найти новую папку 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: 64bit в C:\Windows\system32` and 32bit in C: \ Windows \ SysWOW64`. Для совместимости в 32- C:\Windows\system32` is mapped to битном процессе C: \ Windows \ SysWOW64`. 64-разрядные процессы запустят 64-разрядные вычисления, 32-разрядные процессы - 32-разрядные вычисления. Не удивительно, что их контрольные суммы разные. Если вы держите файл открытым и просматриваете с помощью handles.exeили Process Explorer, вы увидите другой путь.
Ричард
25
@Jon Это то, что известно как перенаправитель файловой системы.
Дэвид Хеффернан
9
@DavidHeffernan Мнения различаются, возможно, наряду с определением «жизнеспособных». Вся эта виртуализация нарушает принцип наименьшего удивления и увеличивает затраты (распределение и время выполнения). Другим операционным системам удается обеспечить как лучшую поддержку 32-на-64, так и лучшую виртуализацию приложений с меньшим количеством ошибок и утечек (попробуйте запустить программы сбора мусора на Wow64 или попробуйте сравнить суммы md5, такие как OP, и несколько других нишевых случаев).
Сехе
5
Иногда мне кажется, что люди голосуют за тебя, потому что ты Джон Скит, а не только из-за ответа. Я не говорю, что ответ не очень хороший или что-то в этом роде, но 145 голосов против, когда ответ «Что-то происходит в Windows» (если честно, вы предоставляете ссылку, но все же), кажется, что люди рассматривают больше, чем просто ваш ответ, когда они голосуют. Я не ненавижу тебя, но это просто означает, что пройдет некоторое время, прежде чем я догоню тебя: P
Джейсон Ридж
5
Блог, как я нашел это. Я надеялся на магию Джона Скита, но я чувствовал, что «Эй, я мог бы сделать это». Вероятно, не так быстро, но вы идете. Хорошо, может быть, я не мог, но все же. Что касается кепки, в ней мало утешения, потому что это просто означает, что в любой день вы ее достигнете, и поэтому я никогда не смогу вас догнать. О, хорошо ...
Джейсон Ридж