Обчисліть контрольну суму 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), що це кращий підхід. Я пройшов сеанс налагодження протягом однієї години, тому що отримую неправильні результати при порівнянні введення користувача з хешем файлів.
fabwu

7
@ wuethrich44, я думаю, що у вас виникає проблема, якщо ви копіюєте / вставляєте код у коментарі aquinas дослівно; Я випадково помітив те саме. Є два невидимих ​​символи - "нульова ширина неприєднувача" та Unicode "нульова ширина простору" - між цитатами "порожні" в необробленому HTML. Я не знаю, чи було це в первинному коментарі, чи винна тут вина.
Кріс Сіммонс

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
Я підтримав вас, тому що більше людей потребує таких дій.
Критичний

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

10
Це перетворює довгий результат у 16 ​​байт у рядок з 16 символів, а не очікуваного шістнадцяткового значення 32 символів.
NiKiZe

3
Цей код не дає очікуваного результату (передбачається очікування). Погоджуючись з @NiKiZe
Нік

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

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 char md5, ніж я міг би очікувати. Це трохи складніше, що я хотів би віддати перевагу, але воно, безумовно, працює.
Смута

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

3

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

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

50
Недоліком використання ReadAllBytesє те, що він завантажує весь файл в один масив. Це взагалі не працює для файлів більше 2 Гб і чинить великий тиск на GC навіть для файлів середнього розміру. Відповідь Джона лише дещо складніша, але не страждає від цих проблем. Тому я віддаю перевагу його відповіді над вашою.
CodesInChaos

1
Помістіть usingодин одного за допомогою перших фігурних фігурних фігурних дужок, 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, то це питання ТА відповідь може бути корисним: хеш MDB, завантажений на Azure, не збігається з тим самим файлом на локальній машині


Якщо ви вважаєте, що відповідь не надто велика, тоді схиляння схиляється нормально. Однак залишення коментаря, що описує причини зниження рівня, допоможе покращити відповіді з часом. Залишаючи коментар із пропозиціями щодо вдосконалення відповіді, ви можете краще сприяти переповненню стека. Дякую!
Манфред
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.