Рассчитать контрольную сумму MD5 для файла


334

Я использую iTextSharp для чтения текста из файла PDF. Однако иногда я не могу извлечь текст, потому что файл PDF содержит только изображения. Я загружаю одни и те же файлы PDF каждый день, и я хочу посмотреть, был ли PDF изменен. Если текст и дата модификации не могут быть получены, является ли контрольная сумма MD5 наиболее надежным способом определить, изменился ли файл?

Если это так, некоторые примеры кода будут оценены, потому что у меня нет большого опыта в криптографии.


Ответы:


773

Это очень просто с помощью System.Security.Cryptography.MD5 :

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

(Я считаю, что на самом деле используемую реализацию MD5 не нужно утилизировать, но я все равно, вероятно, все равно буду это делать.)

Как вы сравниваете результаты впоследствии, зависит от вас; например, вы можете преобразовать байтовый массив в base64 или сравнить байты напрямую. (Просто учтите, что массивы не переопределяются Equals. Использование base64 проще для правильного понимания, но немного менее эффективно, если вы действительно заинтересованы только в сравнении хешей.)

Если вам нужно представить хеш в виде строки, вы можете преобразовать его в шестнадцатеричный код, используя 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
Если вам нужен «стандартный» вид md5, вы можете сделать: returnBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquinas

78
MD5 находится в System.Security.Cryptography - просто для более подробной информации.
Ганс

6
@KalaJ: Если вы пытаетесь обнаружить преднамеренное вмешательство, тогда CRC32 совершенно неуместен. Если вы говорите только о выявлении сбоев при передаче данных, это нормально. Лично я бы, вероятно, использовал SHA-256 просто по привычке :) Я не знаю о поддержке CRC32 в .NET, но вы, вероятно, можете искать его так быстро, как я :)
Джон Скит

12
@ Aquinas Я думаю .Replace("-", String.Empty), это лучший подход. Я прошел сеанс отладки в течение одного часа, потому что я получаю неправильные результаты при сравнении ввода пользователя с хэшем файла.
17

7
@ wuethrich44, я думаю, проблема в том, что вы копируете / вставляете код в комментариях к aquinas дословно; Я случайно заметил то же самое. Между «пустыми» кавычками в необработанном HTML есть два невидимых символа - «не присоединяющийся к нулевой ширине» и Unicode - «пространство нулевой ширины». Я не знаю, было ли это в оригинальном комментарии, или SO здесь виноват.
Крис Симмонс

66

Вот как я это делаю:

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
Я проголосовал за тебя, потому что больше людей должны делать такие вещи.
Krythic

6
Я думаю, что обмен usingблоков был бы полезен, потому что открытие файла более вероятно потерпит неудачу. Сбой раннего / быстрого подхода экономит ресурсы, необходимые для создания (и уничтожения) экземпляра MD5 в таких сценариях. Также вы можете опустить скобки первых usingи сохранить уровень отступа без потери читабельности.
Palec

10
Это преобразует результат длиной 16 байтов в строку из 16 символов, а не ожидаемое шестнадцатеричное значение из 32 символов.
NiKiZe

3
Этот код не дает ожидаемого результата (предполагаемое ожидание). Согласиться с @NiKiZe
Ник

1
@ Quibblesome, я просто пытался продвигать общую идею, что порядок вложенности использования операторов имеет значение. В других местах разница может быть значительной. Почему бы не практиковать привычку обнаруживать неудачу на ранней стадии? Я согласен, однако, что в этом конкретном фрагменте привычка почти не приносит пользы.
Palec

7

Я знаю, что на этот вопрос уже был дан ответ, но вот что я использую:

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

Вероятно, не самый лучший способ, но это может быть удобно.


Я сделал небольшое изменение в вашей функции GetHash. Я превратил его в метод расширения и удалил код отражения.
Лесли Маршалл

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(); }
Лесли Маршалл

Это на самом деле сработало .... спасибо! Я потратил много времени на поиск в Интернете результата, который мог бы привести к нормальной 32-символьной строке md5, чем я ожидал. Это немного сложнее, что я бы предпочел, но это определенно работает.
Troublesum

1
@LeslieMarshall, если вы собираетесь использовать его как метод расширения, вам следует сбросить местоположение потока, а не оставлять его в конечной позиции
MikeT

3

Вот немного более простая версия, которую я нашел. Он читает весь файл за один раз и требует только одной usingдирективы.

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

50
Недостатком использования ReadAllBytesявляется то, что он загружает весь файл в один массив. Это совсем не работает для файлов размером более 2 ГБ и оказывает большое давление на ГХ даже для файлов среднего размера. Ответ Джона немного сложнее, но не страдает от этих проблем. Поэтому я предпочитаю его ответ твоему.
CodesInChaos

1
Поместите буквы « usings» друг за другом без первых фигурных скобок, что using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))дает вам по одному использованию на строку без лишних отступов.
NiKiZe

3
@NiKiZe Вы можете поместить всю программу в одну строку и исключить ВСЕ отступы. Вы даже можете использовать XYZ в качестве имен переменных! Какая польза для других?
Дерек Джонсон

@DerekJohnson, что я пытался сделать, было, вероятно, что «и требует только одну usingдирективу». не было действительно хорошей причины читать все в память. Более эффективный подход состоит в том, чтобы передавать данные в ComputeHashи, если возможно, usingследует использовать только, но я могу полностью понять, хотите ли вы избежать дополнительного уровня отступов.
NiKiZe

3

Я знаю, что опоздал на вечеринку, но выполнил тест, прежде чем на самом деле реализовать решение.

Я провел тест на встроенный класс MD5, а также md5sum.exe . В моем случае встроенный класс занял 13 секунд, где md5sum.exe тоже примерно 16-18 секунд при каждом запуске.

    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

И если вам нужно вычислить MD5, чтобы увидеть, соответствует ли он MD5 большого двоичного объекта Azure, то этот вопрос и ответ SO могут оказаться полезными: MD5-хэш большого двоичного объекта, загруженный в Azure, не совпадает с тем же файлом на локальном компьютере.


Если вы думаете, что ответ не велик, то понижение в порядке. Тем не менее, оставив комментарий с описанием причин для downvoate поможет улучшить ответы с течением времени. Оставив комментарий с предложениями по улучшению ответа, вы можете внести свой вклад в переполнение стека. Спасибо!
Манфред
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.