Calcola checksum MD5 per un file


334

Sto usando iTextSharp per leggere il testo da un file PDF. Tuttavia, ci sono volte in cui non riesco a estrarre il testo, perché il file PDF contiene solo immagini. Scarico gli stessi file PDF ogni giorno e voglio vedere se il PDF è stato modificato. Se non è possibile ottenere il testo e la data di modifica, un checksum MD5 è il modo più affidabile per sapere se il file è cambiato?

Se lo è, alcuni esempi di codice sarebbero apprezzati, perché non ho molta esperienza con la crittografia.


Risposte:


773

È molto semplice utilizzare System.Security.Cryptography.MD5 :

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

(Credo che in realtà l'implementazione MD5 utilizzata non debba essere eliminata, ma probabilmente lo farei comunque.)

Come confrontare i risultati in seguito dipende da te; ad esempio, è possibile convertire l'array di byte in base64 o confrontare direttamente i byte. (Basta essere consapevoli del fatto che le matrici non hanno la precedenza Equals. L'uso di base64 è più semplice da ottenere, ma leggermente meno efficiente se si è interessati solo a confrontare gli hash.)

Se devi rappresentare l'hash come stringa, puoi convertirlo in esadecimale usando 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();
        }
    }
}

251
Se vuoi lo "standard" dall'aspetto md5, puoi farlo: returnBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas

78
MD5 è in System.Security.Cryptography - solo per far emergere di più le informazioni.
Hans,

6
@KalaJ: se stai cercando di individuare manomissioni intenzionali, CRC32 è del tutto inappropriato. Se stai solo parlando di errori di trasferimento dei dati, va bene. Personalmente probabilmente userei SHA-256 solo per abitudine :) Non so del supporto per CRC32 in .NET con disinvoltura, ma probabilmente puoi cercarlo il più velocemente possibile :)
Jon Skeet

12
@aquinas Penso che .Replace("-", String.Empty)sia un approccio migliore. Ho passato una sessione di debug di un'ora perché ottengo risultati errati confrontando l'input di un utente con l'hash del file.
fabwu,

7
@ wuethrich44, penso che il problema che stai riscontrando è se copi / incolli il codice nel commento di Aquinas alla lettera; Mi è capitato di notare la stessa cosa. Esistono due caratteri invisibili - uno "non-joiner di larghezza zero" e uno "spazio di larghezza zero" Unicode - tra le virgolette "vuote" nel codice HTML non elaborato. Non so se fosse nel commento originale o se la colpa è di SO.
Chris Simmons,

66

Ecco come lo faccio:

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

2
Ti ho votato perché più persone hanno bisogno di fare cose del genere.
Krythic,

6
Penso che scambiare i usingblocchi sarebbe utile, perché l'apertura di un file molto probabilmente fallirà. L'approccio Fail / Early rapido consente di risparmiare le risorse necessarie per creare (e distruggere) l'istanza MD5 in tali scenari. Inoltre puoi omettere le parentesi graffe del primo usinge salvare un livello di rientro senza perdere la leggibilità.
Palec,

10
Ciò converte il risultato lungo 16 byte in una stringa di 16 caratteri, non nel valore esadecimale previsto di 32 caratteri.
NiKiZe,

3
Questo codice non produce il risultato atteso (aspettativa attesa). Accordo con @NiKiZe
Nick,

1
@Quibblesome, stavo solo cercando di promuovere l'idea generale secondo cui l'ordine di nidificazione dell'utilizzo delle istruzioni è importante. Altrove, la differenza potrebbe essere significativa. Perché non praticare l'abitudine di rilevare precocemente i guasti? Concordo, tuttavia, che in questo specifico frammento l'abitudine non porta quasi alcun beneficio.
Palec

7

So che questa domanda ha già ricevuto una risposta, ma questo è quello che uso:

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

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

Probabilmente non è il modo migliore, ma può essere utile.


Ho apportato una piccola modifica alla tua funzione GetHash. L'ho trasformato in un metodo di estensione e rimosso il codice di riflessione.
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,

In realtà ha funzionato .... grazie !. Ho impiegato molto tempo a cercare online il risultato che avrebbe prodotto una normale stringa md5 da 32 caratteri di quanto mi aspettassi. Questo è un po 'più complicato che preferirei, ma sicuramente funziona.
Troublesum,

1
@LeslieMarshall se lo utilizzerai come metodo di estensione, dovresti reimpostare la posizione del flusso piuttosto che lasciarla nella posizione finale
MikeT

3

Ecco una versione leggermente più semplice che ho trovato. Legge l'intero file in una volta sola e richiede solo una singola usingdirettiva.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}

50
Il rovescio della medaglia dell'utilizzo ReadAllBytesè che carica l'intero file in un singolo array. Questo non funziona affatto per file di dimensioni superiori a 2 GiB e fa molta pressione sul GC anche per file di medie dimensioni. La risposta di Jon è solo leggermente più complessa, ma non soffre di questi problemi. Quindi preferisco la sua risposta alla tua.
CodesInCos

1
Mettere le usings una dopo l'altra senza le prime parentesi graffe using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))ti dà uno che usa per riga senza indentazioni inutili.
NiKiZe,

3
@NiKiZe Puoi mettere un intero programma su una riga ed eliminare TUTTO il rientro. Puoi persino usare XYZ come nomi di variabili! Qual è il vantaggio per gli altri?
Derek Johnson,

@DerekJohnson il punto che stavo cercando di sottolineare era probabilmente "e richiede solo una singola usingdirettiva". non era davvero un buon motivo per leggere tutto in memoria. L'approccio più efficace consiste nello streaming dei dati ComputeHashe, se possibile, usingdovrebbe essere utilizzato solo, ma posso capire totalmente se si desidera evitare il livello aggiuntivo di rientro.
NiKiZe,

3

So che sono in ritardo per festeggiare ma ho eseguito i test prima di implementare effettivamente la soluzione.

Ho eseguito test contro la classe MD5 integrata e anche md5sum.exe . Nel mio caso la classe integrata ha impiegato 13 secondi, in cui anche md5sum.exe si aggirava intorno ai 16-18 secondi in ogni corsa.

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

2

E se è necessario calcolare l'MD5 per vedere se corrisponde all'MD5 di un BLOB di Azure, allora questa domanda e risposta SO potrebbe essere utile: l' hash MD5 del BLOB caricato su Azure non corrisponde allo stesso file sul computer locale


Se ritieni che la risposta non sia eccezionale, il downvoting va bene. Tuttavia, lasciare un commento che descriva i motivi del downvoate contribuirebbe a migliorare le risposte nel tempo. Lasciando un commento con suggerimenti per migliorare una risposta, puoi contribuire meglio allo Stack Overflow. Grazie!
Manfred,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.