Oblicz sumę kontrolną MD5 dla pliku

334

Korzystam z iTextSharp, aby odczytać tekst z pliku PDF. Czasami jednak nie mogę wyodrębnić tekstu, ponieważ plik PDF zawiera tylko obrazy. Codziennie pobieram te same pliki PDF i chcę sprawdzić, czy plik PDF został zmodyfikowany. Jeśli nie można uzyskać tekstu i daty modyfikacji, czy suma kontrolna MD5 jest najbardziej wiarygodnym sposobem stwierdzenia, czy plik się zmienił?

Jeśli tak, niektóre próbki kodu byłyby mile widziane, ponieważ nie mam dużego doświadczenia z kryptografią.

zepsuł się
źródło

Odpowiedzi:

773

Korzystanie z System.Security.Cryptography.MD5 jest bardzo proste :

using (var md5 = MD5.Create())
{
    using (var stream = File.OpenRead(filename))
    {
        return md5.ComputeHash(stream);
    }
}

(Uważam, że faktycznie wykorzystana implementacja MD5 nie musi być usuwana, ale prawdopodobnie i tak bym to zrobił).

To, jak porównasz wyniki później, zależy od ciebie; możesz na przykład przekonwertować tablicę bajtów na base64 lub bezpośrednio porównać bajty. (Pamiętaj tylko, że tablice się nie zastępują Equals. Korzystanie z base64 jest prostsze, ale nieco mniej wydajne, jeśli naprawdę chcesz porównać hasze).

Jeśli chcesz przedstawić skrót jako ciąg, możesz go przekonwertować na szesnastkowy, używając BitConverter:

static string CalculateMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            var hash = md5.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }
}
Jon Skeet
źródło
251
Jeśli chcesz mieć „standardowy” wyglądający md5, możesz zrobić: returnBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas
78
MD5 jest w System.Security.Cryptography - tylko po to, aby uzyskać więcej informacji.
Hans
6
@KalaJ: Jeśli próbujesz wykryć celowe manipulowanie, CRC32 jest całkowicie nieodpowiedni. Jeśli mówisz tylko o wykrywaniu błędów transferu danych, nie ma problemu. Osobiście prawdopodobnie użyłbym SHA-256 po prostu z przyzwyczajenia :) Nie wiem o obsłudze CRC32 w .NET od ręki, ale prawdopodobnie możesz go wyszukać tak szybko, jak to możliwe :)
Jon Skeet
12
@aquinas Myślę, że .Replace("-", String.Empty)to lepsze podejście. Przeszedłem jednogodzinną sesję debugowania, ponieważ otrzymuję błędne wyniki podczas porównywania danych wejściowych użytkownika z skrótem pliku.
fabwu
7
@ wuethrich44, myślę, że masz problem, jeśli kopiujesz / wklejasz kod w komentarzu do akwarium dosłownie; Zdarzyło mi się zauważyć to samo. Pomiędzy „pustymi” cudzysłowami w surowym kodzie HTML znajdują się dwa niewidoczne znaki - „łącznik o zerowej szerokości” i „przestrzeń zerowa” w Unicode. Nie wiem, czy było to w oryginalnym komentarzu, czy SO jest tutaj winna.
Chris Simmons,
66

Tak to robię:

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

public string checkMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            return Encoding.Default.GetString(md5.ComputeHash(stream));
        }
    }
}
BoliBerrys
źródło
2
Głosowałem za tobą, ponieważ więcej osób musi robić takie rzeczy.
Krythic
6
Myślę, że zamiana usingbloków byłaby przydatna, ponieważ otwarcie pliku najprawdopodobniej zakończy się niepowodzeniem. Niepowodzenie wczesnego / szybkiego podejścia oszczędza zasoby potrzebne do utworzenia (i zniszczenia) instancji MD5 w takich scenariuszach. Możesz także pominąć nawiasy klamrowe pierwszego usingi zapisać poziom wcięcia bez utraty czytelności.
Palec
10
Konwertuje to wynik o długości 16 bajtów na ciąg 16 znaków, a nie oczekiwaną wartość szesnastkową 32 znaków.
NiKiZe
3
Ten kod nie daje oczekiwanego wyniku (zakładane oczekiwanie). Zgadzam się z @NiKiZe
Nick
1
@Quibblesome, właśnie próbowałem promować ogólną ideę, że kolejność zagnieżdżania instrukcji jest ważna. Gdzie indziej różnica może być znacząca. Dlaczego nie ćwiczyć nawyku wczesnego wykrywania awarii? Zgadzam się jednak, że w tym konkretnym fragmencie zwyczaj nie przynosi prawie żadnych korzyści.
Palec
7

Wiem, że na to pytanie już udzielono odpowiedzi, ale używam tego:

using (FileStream fStream = File.OpenRead(filename)) {
    return GetHash<MD5>(fStream)
}

Gdzie GetHash :

public static String GetHash<T>(Stream stream) where T : HashAlgorithm {
    StringBuilder sb = new StringBuilder();

    MethodInfo create = typeof(T).GetMethod("Create", new Type[] {});
    using (T crypt = (T) create.Invoke(null, null)) {
        byte[] hashBytes = crypt.ComputeHash(stream);
        foreach (byte bt in hashBytes) {
            sb.Append(bt.ToString("x2"));
        }
    }
    return sb.ToString();
}

Prawdopodobnie nie najlepszy sposób, ale może się przydać.

Badaro Jr.
źródło
Wprowadziłem niewielką zmianę w funkcji GetHash. Zmieniłem go na metodę rozszerzenia i usunąłem kod odbicia.
Leslie Marshall
3
public static String GetHash<T>(this Stream stream) where T : HashAlgorithm, new() { StringBuilder sb = new StringBuilder(); using (T crypt = new T()) { byte[] hashBytes = crypt.ComputeHash(stream); foreach (byte bt in hashBytes) { sb.Append(bt.ToString("x2")); } } return sb.ToString(); }
Leslie Marshall
To faktycznie działało ... dziękuję! Długo szukałem w Internecie wyników, które dałyby normalny ciąg 32 znaków md5, niż się spodziewałem. To trochę bardziej skomplikowane, niż wolałbym, ale na pewno działa.
Troublesum
1
@LeslieMarshall, jeśli zamierzasz użyć go jako metody rozszerzenia, powinieneś zresetować lokalizację strumienia, zamiast pozostawiać go w pozycji końcowej
MikeT
3

Oto nieco prostsza wersja, którą znalazłem. Czyta cały plik za jednym razem i wymaga tylko jednej usingdyrektywy.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}
Ashley Davis
źródło
50
Minusem używania ReadAllBytesjest to, że ładuje cały plik do jednej tablicy. To w ogóle nie działa w przypadku plików większych niż 2 GiB i wywiera duży nacisk na GC, nawet w przypadku plików średnich. Odpowiedź Jona jest tylko nieco bardziej złożona, ale nie cierpi z powodu tych problemów. Wolę więc jego odpowiedź niż twoją.
CodesInChaos
1
Umieszczenie w usingkolejności s bez pierwszych nawiasów klamrowych using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))daje jedno użycie w linii bez niepotrzebnego wcięcia.
NiKiZe
3
@NiKiZe Możesz umieścić cały program w jednym wierszu i wyeliminować WSZYSTKIE wcięcia. Możesz nawet używać XYZ jako nazw zmiennych! Jakie są korzyści dla innych?
Derek Johnson
@DerekJohnson chciałem powiedzieć, że „prawdopodobnie wymaga tylko jednej usingdyrektywy”. nie był tak naprawdę dobrym powodem, aby wszystko odczytywać w pamięci. Bardziej efektywnym podejściem jest przesyłanie strumieniowe danych ComputeHashi, jeśli to możliwe, usingpowinno się je stosować, ale całkowicie rozumiem, czy chcesz uniknąć dodatkowego poziomu wcięcia.
NiKiZe,
3

Wiem, że jestem spóźniony na imprezę, ale przeprowadziłem test, zanim wdrożyłem rozwiązanie.

Zrobiłem test przeciwko wbudowanej klasie MD5, a także md5sum.exe . W moim przypadku wbudowana klasa zajęła 13 sekund, podczas gdy md5sum.exe również około 16-18 sekund przy każdym uruchomieniu.

    DateTime current = DateTime.Now;
    string file = @"C:\text.iso";//It's 2.5 Gb file
    string output;
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(file))
        {
            byte[] checksum = md5.ComputeHash(stream);
            output = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
            Console.WriteLine("Total seconds : " + (DateTime.Now - current).TotalSeconds.ToString() + " " + output);
        }
    }
Romil Kumar Jain
źródło
2

A jeśli musisz obliczyć MD5, aby zobaczyć, czy pasuje on do MD5 obiektu blob platformy Azure, to to SO pytanie i odpowiedź może być pomocne: skrót MD5 obiektu blob przesłanego na platformę Azure nie pasuje do tego samego pliku na komputerze lokalnym

Manfred
źródło
Jeśli uważasz, że odpowiedź nie jest świetna, głosowanie w dół jest w porządku. Jednak pozostawienie komentarza opisującego przyczyny zaległości pomogłoby z czasem poprawić odpowiedzi. Pozostawiając komentarz z sugestiami dotyczącymi poprawy odpowiedzi, możesz lepiej przyczynić się do przepełnienia stosu. Dzięki!
Manfred,