Calcular la suma de comprobación MD5 para un archivo

334

Estoy usando iTextSharp para leer el texto de un archivo PDF. Sin embargo, hay veces que no puedo extraer texto, porque el archivo PDF solo contiene imágenes. Descargo los mismos archivos PDF todos los días, y quiero ver si el PDF ha sido modificado. Si no se puede obtener el texto y la fecha de modificación, ¿es la suma de verificación MD5 la forma más confiable de saber si el archivo ha cambiado?

Si es así, se agradecerían algunos ejemplos de código, porque no tengo mucha experiencia con la criptografía.

rompió
fuente

Respuestas:

773

Es muy simple usar System.Security.Cryptography.MD5 :

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

(Creo que en realidad la implementación de MD5 utilizada no necesita ser eliminada, pero probablemente todavía lo haría de todos modos).

La forma de comparar los resultados después depende de usted; puede convertir la matriz de bytes a base64, por ejemplo, o comparar los bytes directamente. (Solo tenga en cuenta que las matrices no se anulan Equals. Usar base64 es más sencillo, pero un poco menos eficiente si realmente solo está interesado en comparar los hashes).

Si necesita representar el hash como una cadena, puede convertirlo a 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
fuente
251
Si desea el aspecto "estándar" md5, puede hacer lo siguiente: volverBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas
78
MD5 está en System.Security.Cryptography, solo para mostrar más información.
Hans
66
@KalaJ: Si está tratando de detectar la manipulación deliberada, CRC32 es completamente inapropiado. Si solo está hablando de detectar fallas en la transferencia de datos, está bien. Personalmente, probablemente usaría SHA-256 por costumbre :) No sé acerca de la compatibilidad con CRC32 en .NET de antemano, pero probablemente pueda buscarlo lo más rápido que pueda :)
Jon Skeet
12
@aquinas creo que .Replace("-", String.Empty)es un mejor enfoque. Pasé por una sesión de depuración de una hora porque obtengo resultados incorrectos al comparar la entrada de un usuario con el hash del archivo.
fabwu 01 de
77
@ wuethrich44, creo que el problema que tiene es si copia / pega el código en el comentario de Aquino al pie de la letra; Me di cuenta de lo mismo. Hay dos caracteres invisibles: un "no ancho de ancho cero" y un "espacio de ancho cero" de Unicode, entre las comillas "vacías" en el HTML sin formato. No sé si estaba en el comentario original o si SO tiene la culpa aquí.
Chris Simmons
66

Así es como lo hago:

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
fuente
2
Te voté porque más personas necesitan hacer cosas como esta.
Krythic
66
Creo que intercambiar los usingbloques sería útil, porque abrir un archivo probablemente fallará. El enfoque de falla temprana / rápida le ahorra los recursos necesarios para crear (y destruir) la instancia MD5 en tales escenarios. También puede omitir las llaves del primero usingy guardar un nivel de sangría sin perder legibilidad.
Palec
10
Esto convierte el resultado de 16 bytes de longitud en una cadena de 16 caracteres, no el valor hexadecimal esperado de 32 caracteres.
NiKiZe
3
Este código no produce el resultado esperado (expectativa asumida). De acuerdo con @NiKiZe
Nick
1
@Quibblesome, solo estaba tratando de promover la idea general de que el orden de anidamiento del uso de declaraciones es importante. En otros lugares, la diferencia puede ser significativa. ¿Por qué no practicar el hábito de detectar fallas temprano? Sin embargo, estoy de acuerdo en que en este fragmento específico, el hábito casi no aporta ningún beneficio.
Palec
7

Sé que esta pregunta ya fue respondida, pero esto es lo que uso:

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

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

Probablemente no sea la mejor manera, pero puede ser útil.

Badaro Jr.
fuente
He realizado un pequeño cambio en su función GetHash. Lo convertí en un método de extensión y eliminé el código de reflexión.
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
Esto realmente funcionó ... ¡gracias! Pasé mucho tiempo buscando en línea el resultado que produciría una cadena normal de 32 caracteres md5 de lo que esperaba. Esto es un poco más complicado de lo que preferiría pero definitivamente funciona.
Problema
1
@LeslieMarshall si va a usarlo como un método de extensión, debe restablecer la ubicación de la transmisión en lugar de dejarla en la posición final
MikeT
3

Aquí hay una versión un poco más simple que encontré. Lee todo el archivo de una vez y solo requiere una sola usingdirectiva.

byte[] ComputeHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        return md5.ComputeHash(File.ReadAllBytes(filePath));
    }
}
Ashley Davis
fuente
50
La desventaja de usar ReadAllByteses que carga todo el archivo en una sola matriz. Eso no funciona en absoluto para archivos de más de 2 GiB y ejerce mucha presión sobre el GC, incluso para archivos de tamaño mediano. La respuesta de Jon es solo un poco más compleja, pero no sufre estos problemas. Así que prefiero su respuesta sobre la tuya.
CodesInChaos
1
Poner la usings una detrás de la otra sin las primeras llaves using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))le da una usando por línea sin sangría innecesaria.
NiKiZe
3
@NiKiZe Puede poner un programa completo en una línea y eliminar TODAS las sangrías. ¡Incluso puedes usar XYZ como nombres de variables! ¿Cuál es el beneficio para los demás?
Derek Johnson el
@DerekJohnson el punto que estaba tratando de hacer fue probablemente "y solo requiere una sola usingdirectiva". No era realmente una buena razón para leer todo en la memoria. El enfoque más efectivo es transmitir los datos ComputeHashy, si es posible using, solo debe usarse, pero puedo entender por completo si desea evitar el nivel adicional de sangría.
NiKiZe
3

Sé que llego tarde a la fiesta, pero realicé la prueba antes de implementar la solución.

Realicé la prueba contra la clase MD5 incorporada y también md5sum.exe . En mi caso, la clase incorporada tomó 13 segundos donde md5sum.exe también alrededor de 16-18 segundos en cada ejecución.

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

Y si necesita calcular el MD5 para ver si coincide con el MD5 de un blob de Azure, entonces esta pregunta y respuesta SO podría ser útil: el hash MD5 del blob cargado en Azure no coincide con el mismo archivo en la máquina local

Manfredo
fuente
Si crees que la respuesta no es excelente, entonces el voto negativo está bien. Sin embargo, dejar un comentario que describa las razones del descenso ayudaría a mejorar las respuestas con el tiempo. Al dejar un comentario con sugerencias para mejorar una respuesta, puede contribuir mejor a Stack Overflow. ¡Gracias!
Manfred