Berechnen Sie die MD5-Prüfsumme für eine Datei

334

Ich verwende iTextSharp , um den Text aus einer PDF-Datei zu lesen. Es gibt jedoch Situationen, in denen ich keinen Text extrahieren kann, da die PDF-Datei nur Bilder enthält. Ich lade jeden Tag die gleichen PDF-Dateien herunter und möchte sehen, ob die PDF-Datei geändert wurde. Wenn der Text und das Änderungsdatum nicht abgerufen werden können, ist eine MD5- Prüfsumme die zuverlässigste Methode, um festzustellen, ob sich die Datei geändert hat?

Wenn ja, wären einige Codebeispiele willkommen, da ich nicht viel Erfahrung mit Kryptographie habe.

Pleite
quelle

Antworten:

773

Mit System.Security.Cryptography.MD5 ist es sehr einfach :

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

(Ich glaube , dass tatsächlich die Umsetzung MD5 nicht entsorgt werden müssen, aber ich würde wahrscheinlich so trotzdem noch tun.)

Wie Sie die Ergebnisse anschließend vergleichen, liegt bei Ihnen. Sie können beispielsweise das Byte-Array in base64 konvertieren oder die Bytes direkt vergleichen. (Beachten Sie jedoch, dass Arrays nicht überschrieben werden Equals. Die Verwendung von base64 ist einfacher, aber etwas weniger effizient, wenn Sie wirklich nur daran interessiert sind, die Hashes zu vergleichen.)

Wenn Sie den Hash als Zeichenfolge darstellen müssen, können Sie ihn mithilfe von 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
quelle
251
Wenn Sie möchten, dass der "Standard" md5 BitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aussieht
78
MD5 befindet sich in System.Security.Cryptography - nur um mehr Informationen zu erhalten.
Hans
6
@KalaJ: Wenn Sie versuchen, vorsätzliche Manipulationen zu erkennen, ist CRC32 völlig unangemessen. Wenn Sie nur über das Erkennen von Datenübertragungsfehlern sprechen, ist dies in Ordnung. Persönlich würde ich SHA-256 wahrscheinlich nur aus Gewohnheit verwenden :) Ich weiß nicht, wie man CRC32 in .NET ohne weiteres unterstützt, aber Sie können wahrscheinlich so schnell wie möglich danach suchen :)
Jon Skeet
12
@aquinas Ich denke, .Replace("-", String.Empty)ist ein besserer Ansatz. Ich habe eine einstündige Debug-Sitzung durchlaufen, weil ich beim Vergleich einer Benutzereingabe mit dem Datei-Hash falsche Ergebnisse erhalte.
Fabwu
7
@ wuethrich44, ich denke, das Problem, das Sie haben, ist, wenn Sie den Code in Aquinas Kommentar wörtlich kopieren / einfügen; Mir ist zufällig dasselbe aufgefallen. Zwischen den "leeren" Anführungszeichen im unformatierten HTML-Code befinden sich zwei unsichtbare Zeichen - ein "Nicht-Joiner" mit der Breite Null und ein Unicode-Leerzeichen mit der Breite Null. Ich weiß nicht, ob es im ursprünglichen Kommentar war oder ob SO hier schuld ist.
Chris Simmons
66

So mache ich es:

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
quelle
2
Ich habe dich positiv bewertet, weil mehr Leute solche Dinge tun müssen.
Krythic
6
Ich denke, das Austauschen der usingBlöcke wäre nützlich, da das Öffnen einer Datei mit größerer Wahrscheinlichkeit fehlschlagen wird. Ein frühzeitiger / schneller Fehleransatz spart Ihnen die Ressourcen, die zum Erstellen (und Zerstören) der MD5-Instanz in solchen Szenarien erforderlich sind. Sie können auch die geschweiften Klammern der ersten weglassen usingund eine Einrückungsstufe speichern, ohne die Lesbarkeit zu verlieren.
Palec
10
Dadurch wird das 16 Byte lange Ergebnis in eine Zeichenfolge mit 16 Zeichen konvertiert, nicht in den erwarteten Hex-Wert von 32 Zeichen.
NiKiZe
3
Dieser Code erzeugt nicht das erwartete Ergebnis (angenommene Erwartung). Zustimmung zu @NiKiZe
Nick
1
@ Quibblesome, ich habe nur versucht, die allgemeine Idee zu fördern, dass die Reihenfolge der Verschachtelung der Verwendung von Anweisungen wichtig ist. An anderer Stelle könnte der Unterschied erheblich sein. Warum nicht die Gewohnheit üben, Fehler frühzeitig zu erkennen? Ich stimme jedoch zu, dass die Gewohnheit in diesem speziellen Ausschnitt fast keinen Nutzen bringt.
Palec
7

Ich weiß, dass diese Frage bereits beantwortet wurde, aber ich verwende Folgendes:

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

Wo 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();
}

Wahrscheinlich nicht der beste Weg, aber es kann praktisch sein.

Badaro Jr.
quelle
Ich habe eine kleine Änderung an Ihrer GetHash-Funktion vorgenommen. Ich habe daraus eine Erweiterungsmethode gemacht und den Reflektionscode entfernt.
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
Das hat tatsächlich funktioniert ... danke!. Ich habe viel zu lange online nach dem Ergebnis gesucht, das eine normale 32-Zeichen-MD5-Zeichenfolge erzeugen würde, als ich erwartet hätte. Dies ist etwas komplizierter, als ich es vorziehen würde, aber es funktioniert definitiv.
Troublesum
1
@LeslieMarshall Wenn Sie es als Erweiterungsmethode verwenden möchten, sollten Sie den Stream-Speicherort zurücksetzen, anstatt ihn an der Endposition zu
belassen
3

Hier ist eine etwas einfachere Version, die ich gefunden habe. Es liest die gesamte Datei auf einmal und erfordert nur eine einzige usingAnweisung.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}
Ashley Davis
quelle
50
Der Nachteil der Verwendung ReadAllBytesist, dass die gesamte Datei in ein einzelnes Array geladen wird. Das funktioniert bei Dateien mit mehr als 2 GiB überhaupt nicht und übt selbst bei mittelgroßen Dateien großen Druck auf den GC aus. Jons Antwort ist nur geringfügig komplexer, leidet aber nicht unter diesen Problemen. Also ziehe ich seine Antwort Ihrer vor.
CodesInChaos
1
Wenn Sie das usings nacheinander ohne die ersten geschweiften Klammern using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))einfügen, erhalten Sie eine Verwendung pro Zeile ohne unnötige Einrückung.
NiKiZe
3
@NiKiZe Sie können ein ganzes Programm in eine Zeile setzen und ALLE Einrückungen entfernen. Sie können sogar XYZ als Variablennamen verwenden! Was ist der Nutzen für andere?
Derek Johnson
@DerekJohnson Der Punkt, den ich ansprechen wollte, war wahrscheinlich, dass "und nur eine einzige usingAnweisung erforderlich ist ". war nicht wirklich ein guter Grund, alles in Erinnerung zu lesen. Der effektivere Ansatz besteht darin, die Daten in Daten zu streamen ComputeHashund wenn möglich usingnur zu verwenden. Ich kann jedoch vollkommen verstehen, ob Sie die zusätzliche Einrückungsstufe vermeiden möchten.
NiKiZe
3

Ich weiß, dass ich zu spät zur Party komme, aber einen Test durchgeführt habe, bevor ich die Lösung tatsächlich implementiert habe.

Ich habe einen Test gegen die eingebaute MD5-Klasse und auch gegen md5sum.exe durchgeführt . In meinem Fall dauerte die eingebaute Klasse 13 Sekunden, wobei md5sum.exe bei jedem Lauf ebenfalls etwa 16 bis 18 Sekunden dauerte.

    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
quelle
2

Wenn Sie den MD5 berechnen müssen, um festzustellen, ob er mit dem MD5 eines Azure-Blobs übereinstimmt, ist diese SO-Frage und Antwort möglicherweise hilfreich: Der auf Azure hochgeladene MD5-Blob-Hash stimmt nicht mit derselben Datei auf dem lokalen Computer überein

Manfred
quelle
Wenn Sie der Meinung sind, dass die Antwort nicht gut ist, ist Downvoting in Ordnung. Das Hinterlassen eines Kommentars, in dem die Gründe für die Absenkung beschrieben werden, würde jedoch dazu beitragen, die Antworten im Laufe der Zeit zu verbessern. Wenn Sie einen Kommentar mit Vorschlägen zur Verbesserung einer Antwort hinterlassen, können Sie besser zum Stapelüberlauf beitragen. Vielen Dank!
Manfred