Calculer la somme de contrôle MD5 pour un fichier

334

J'utilise iTextSharp pour lire le texte d'un fichier PDF. Cependant, il m'arrive de ne pas pouvoir extraire de texte, car le fichier PDF ne contient que des images. Je télécharge les mêmes fichiers PDF tous les jours et je veux voir si le PDF a été modifié. Si le texte et la date de modification ne peuvent pas être obtenus, une somme de contrôle MD5 est-elle le moyen le plus fiable de savoir si le fichier a changé?

Si c'est le cas, certains exemples de code seraient appréciés, car je n'ai pas beaucoup d'expérience avec la cryptographie.

cassé
la source

Réponses:

773

C'est très simple en utilisant System.Security.Cryptography.MD5 :

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

(Je crois qu'en réalité l'implémentation MD5 utilisée n'a pas besoin d'être supprimée, mais je le ferais probablement quand même.)

La façon dont vous comparez les résultats par la suite dépend de vous; vous pouvez convertir le tableau d'octets en base64 par exemple, ou comparer directement les octets. (Sachez simplement que les tableaux ne remplacent pas Equals. L'utilisation de base64 est plus simple à obtenir, mais légèrement moins efficace si vous êtes vraiment intéressé à comparer les hachages.)

Si vous devez représenter le hachage sous forme de chaîne, vous pouvez le convertir en hexadécimal en utilisant 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
la source
251
Si vous voulez le md5 "standard", vous pouvez faire: retourBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas
78
MD5 est dans System.Security.Cryptography - juste pour faire apparaître plus d'informations.
Hans
6
@KalaJ: Si vous essayez de repérer une falsification délibérée, CRC32 est totalement inapproprié. Si vous ne parlez que de repérer les échecs de transfert de données, ça va. Personnellement, j'utiliserais probablement SHA-256 juste par habitude :) Je ne connais pas la prise en charge de CRC32 dans .NET, mais vous pouvez probablement le rechercher aussi rapidement que possible :)
Jon Skeet
12
@aquinas Je pense que .Replace("-", String.Empty)c'est une meilleure approche. Je suis passé par une session de débogage d'une heure parce que j'obtiens des résultats erronés lorsque je compare une entrée utilisateur au hachage de fichier.
fabwu
7
@ wuethrich44, je pense que le problème que vous rencontrez est si vous copiez / collez le code dans les commentaires aquinas textuellement; J'ai remarqué la même chose. Il y a deux caractères invisibles - un "non-joiner de largeur nulle" et un "espace de largeur nulle" Unicode - entre les guillemets "vides" dans le HTML brut. Je ne sais pas si c'était dans le commentaire original ou si c'est à blâmer ici.
Chris Simmons
66

Voici comment je le fais:

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
la source
2
Je vous ai voté parce que plus de gens ont besoin de faire des choses comme ça.
Krythic
6
Je pense que l'échange des usingblocs serait utile, car l'ouverture d'un fichier va probablement échouer. L'approche échec précoce / rapide vous permet d'économiser les ressources nécessaires pour créer (et détruire) l'instance MD5 dans de tels scénarios. Vous pouvez également omettre les accolades de la première usinget enregistrer un niveau d'indentation sans perdre en lisibilité.
Palec
10
Cela convertit le résultat de 16 octets en une chaîne de 16 caractères, et non la valeur hexadécimale attendue de 32 caractères.
NiKiZe
3
Ce code ne produit pas le résultat attendu (attente supposée). En accord avec @NiKiZe
Nick
1
@Quibblesome, j'essayais simplement de promouvoir l'idée générale que l'ordre d'imbrication des instructions d'utilisation est important. Ailleurs, la différence pourrait être importante. Pourquoi ne pas prendre l'habitude de détecter précocement l'échec? Je suis d'accord, cependant, que dans cet extrait spécifique, l'habitude n'apporte presque aucun avantage.
Palec du
7

Je sais que cette question a déjà été répondue, mais voici ce que j'utilise:

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

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

Probablement pas la meilleure façon, mais cela peut être pratique.

Badaro Jr.
la source
J'ai apporté une petite modification à votre fonction GetHash. Je l'ai transformé en une méthode d'extension et supprimé le code de réflexion.
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
Cela a réellement fonctionné .... merci !. J'ai passé bien trop longtemps à chercher en ligne le résultat qui produirait une chaîne md5 de 32 caractères normale que ce à quoi je m'attendais. C'est un peu plus compliqué que je préférerais mais ça marche définitivement.
Troublesum
1
@LeslieMarshall si vous allez l'utiliser comme méthode d'extension, vous devez réinitialiser l'emplacement du flux plutôt que de le laisser à la position finale
finale
3

Voici une version légèrement plus simple que j'ai trouvée. Il lit le fichier entier en une seule fois et ne nécessite qu'une seule usingdirective.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}
Ashley Davis
la source
50
L'inconvénient de l'utilisation ReadAllBytesest qu'il charge tout le fichier dans un seul tableau. Cela ne fonctionne pas du tout pour les fichiers de plus de 2 Gio et met beaucoup de pression sur le GC, même pour les fichiers de taille moyenne. La réponse de Jon n'est que légèrement plus complexe, mais ne souffre pas de ces problèmes. Je préfère donc sa réponse à la vôtre.
CodesInChaos
1
Mettez les usings après l'autre sans les premières accolades using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))vous donne une utilisation par ligne sans indentation inutile.
NiKiZe
3
@NiKiZe Vous pouvez mettre un programme entier sur une seule ligne et éliminer TOUT indentation. Vous pouvez même utiliser XYZ comme noms de variables! Quel est l'avantage pour les autres?
Derek Johnson
@DerekJohnson le point que j'essayais de faire était probablement que "et ne nécessite qu'une seule usingdirective." n'était pas vraiment une bonne raison de tout lire en mémoire. L'approche la plus efficace consiste à diffuser les données dans ComputeHash, et si possible à usingutiliser uniquement, mais je peux totalement comprendre si vous voulez éviter le niveau supplémentaire d'indentation.
NiKiZe
3

Je sais que je suis en retard pour faire la fête, mais j'ai effectué des tests avant d'implémenter la solution.

J'ai effectué un test contre la classe MD5 intégrée et également md5sum.exe . Dans mon cas, la classe intégrée a pris 13 secondes où md5sum.exe aussi environ 16-18 secondes à chaque exécution.

    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
la source
2

Et si vous devez calculer le MD5 pour voir s'il correspond au MD5 d'un blob Azure, cette question et réponse SO pourraient être utiles: le hachage MD5 du blob téléchargé sur Azure ne correspond pas au même fichier sur la machine locale

Manfred
la source
Si vous pensez que la réponse n'est pas bonne, alors le vote négatif est bien. Cependant, le fait de laisser un commentaire décrivant les raisons de la baisse de la facture contribuerait à améliorer les réponses au fil du temps. En laissant un commentaire avec des suggestions pour améliorer une réponse, vous pouvez mieux contribuer à Stack Overflow. Merci!
Manfred