Calcular a soma de verificação MD5 para um arquivo

334

Estou usando o iTextSharp para ler o texto de um arquivo PDF. No entanto, há momentos em que não consigo extrair texto, porque o arquivo PDF contém apenas imagens. Faço o download dos mesmos arquivos PDF todos os dias e quero ver se o PDF foi modificado. Se o texto e a data da modificação não puderem ser obtidos, uma soma de verificação MD5 é a maneira mais confiável de saber se o arquivo foi alterado?

Se for, alguns exemplos de código seriam apreciados, porque eu não tenho muita experiência com criptografia.

quebrou
fonte

Respostas:

773

É muito simples usar System.Security.Cryptography.MD5 :

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

(Acredito que, na verdade, a implementação do MD5 usada não precisa ser descartada, mas provavelmente ainda o faria.)

Como você compara os resultados depois é com você; você pode converter a matriz de bytes em base64, por exemplo, ou comparar os bytes diretamente. (Lembre-se de que as matrizes não substituem Equals. Usar o base64 é mais simples de acertar, mas um pouco menos eficiente se você estiver realmente interessado apenas em comparar os hashes.)

Se você precisar representar o hash como uma sequência, poderá convertê-lo em hexadecimal 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();
        }
    }
}
Jon Skeet
fonte
251
Se quiser que o olhar md5 "standard", você pode fazer: retornoBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas
78
O MD5 está em System.Security.Cryptography - apenas para exibir mais informações.
Hans
6
@KalaJ: Se você está tentando identificar adulterações deliberadas, o CRC32 é totalmente inapropriado. Se você está falando apenas de detectar falhas na transferência de dados, tudo bem. Pessoalmente, eu provavelmente usar SHA-256 apenas por força do hábito :) Eu não sei sobre o suporte para CRC32 em .NET improviso, mas provavelmente você pode procurá-lo tão rapidamente como eu pode :)
Jon Skeet
12
@aquinas Eu acho que .Replace("-", String.Empty)é uma abordagem melhor. Passei por uma sessão de depuração de uma hora porque obtenho resultados errados ao comparar uma entrada do usuário com o hash do arquivo.
fabwu
7
@ wuethrich44, acho que o problema que você está enfrentando é se você copiar / colar o código em aquinas, comentar literalmente; Por acaso notei a mesma coisa. Existem dois caracteres invisíveis - um "não marceneiro de largura zero" e um "espaço de largura zero" Unicode - entre as aspas "vazias" no HTML bruto. Não sei se foi no comentário original ou se SO é o culpado aqui.
21917 Chris Chris Simmons
66

É assim que eu faço:

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
fonte
2
Eu votei em você porque mais pessoas precisam fazer coisas assim.
Krythic
6
Eu acho que a troca dos usingblocos seria útil, porque a abertura de um arquivo provavelmente falhará. A abordagem de falha antecipada / rápida economiza os recursos necessários para criar (e destruir) a instância MD5 nesses cenários. Além disso, você pode omitir as chaves do primeiro usinge salvar um nível de indentação sem perder a legibilidade.
Palec
10
Isso converte o resultado de 16 bytes em uma sequência de 16 caracteres, não no valor hexadecimal esperado de 32 caracteres.
NiKiZe
3
Este código não produz o resultado esperado (expectativa assumida). Concordando com @NiKiZe
Nick
1
@ Quibblesome, eu estava apenas tentando promover a idéia geral de que a ordem de aninhamento do uso de instruções é importante. Em outros lugares, a diferença pode ser significativa. Por que não praticar o hábito de detectar falhas antecipadamente? Concordo, porém, que neste trecho específico, o hábito quase não traz benefícios.
Palec 8/01/19
7

Eu sei que esta pergunta já foi respondida, mas é isso que eu uso:

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

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

Provavelmente não é o melhor caminho, mas pode ser útil.

Badaro Jr.
fonte
Fiz uma pequena alteração na sua função GetHash. Eu o transformei em um método de extensão e removi o código de reflexão.
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
Isso realmente funcionou .... obrigado !. Passei muito tempo pesquisando on-line o resultado que produziria uma string normal de 32 char md5 do que eu esperava. Isso é um pouco mais complicado do que eu prefiro, mas definitivamente funciona.
Troublesum
1
@LeslieMarshall se você estiver indo para usá-lo como um método de extensão, então você deve redefinir a localização fluxo ao invés de deixá-lo na posição final
miket
3

Aqui está uma versão um pouco mais simples que eu encontrei. Ele lê o arquivo inteiro de uma só vez e requer apenas uma única usingdiretiva.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}
Ashley Davis
fonte
50
A desvantagem de usar ReadAllBytesé que ele carrega o arquivo inteiro em uma única matriz. Isso não funciona em arquivos maiores que 2 GiB e coloca muita pressão no GC, mesmo em arquivos de tamanho médio. A resposta de Jon é apenas um pouco mais complexa, mas não sofre com esses problemas. Então, eu prefiro a resposta dele à sua.
CodesInChaos
1
Colocar os usings um após o outro sem os primeiros chavetas using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))proporciona um uso por linha sem recuo desnecessário.
NiKiZe
3
@NiKiZe Você pode colocar um programa inteiro em uma linha e eliminar TODO o recuo. Você pode até usar XYZ como nomes de variáveis! Qual é o benefício para os outros?
Derek Johnson
@DerekJohnson, o que eu estava tentando dizer era provavelmente "e requer apenas uma única usingdiretiva". Não era realmente um bom motivo para ler tudo na memória. A abordagem mais eficaz é transmitir os dados para ComputeHash, e, se possível, usingdeve ser usada apenas, mas posso entender totalmente se você deseja evitar o nível extra de indentação.
NiKiZe 12/08
3

Sei que estou atrasado para a festa, mas realizei o teste antes de realmente implementar a solução.

Eu realizei teste contra a classe MD5 embutida e também o md5sum.exe . No meu caso, a classe incorporada levou 13 segundos, enquanto o md5sum.exe também ficou em torno de 16 a 18 segundos em cada execução.

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

E se você precisar calcular o MD5 para verificar se ele corresponde ao MD5 de um blob do Azure, esta pergunta e resposta do SO podem ser úteis: O hash do MD5 do blob carregado no Azure não corresponde ao mesmo arquivo na máquina local

Manfred
fonte
Se você acha que a resposta não é ótima, a redução de votos é boa. No entanto, deixar um comentário descrevendo os motivos do downvoate ajudaria a melhorar as respostas ao longo do tempo. Ao deixar um comentário com sugestões para melhorar uma resposta, você pode contribuir melhor para o Stack Overflow. Obrigado!
Manfred