Tính toán tổng kiểm MD5 cho một tệp


334

Tôi đang sử dụng iTextSharp để đọc văn bản từ tệp PDF. Tuy nhiên, có những lúc tôi không thể trích xuất văn bản, vì tệp PDF chỉ chứa hình ảnh. Tôi tải xuống cùng một tệp PDF hàng ngày và tôi muốn xem liệu PDF đã được sửa đổi chưa. Nếu không thể lấy được văn bản và ngày sửa đổi, thì tổng kiểm tra MD5 có phải là cách đáng tin cậy nhất để biết liệu tệp đã thay đổi không?

Nếu đúng như vậy, một số mẫu mã sẽ được đánh giá cao, vì tôi không có nhiều kinh nghiệm về mật mã.


Câu trả lời:


773

Thật đơn giản khi sử dụng System.Security.Cryptography.MD5 :

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

(Tôi tin rằng thực tế việc triển khai MD5 được sử dụng không cần phải xử lý, nhưng dù sao tôi vẫn có thể làm như vậy.)

Làm thế nào bạn so sánh kết quả sau đó là tùy thuộc vào bạn; bạn có thể chuyển đổi mảng byte thành base64 chẳng hạn, hoặc so sánh trực tiếp các byte. (Chỉ cần lưu ý rằng các mảng không ghi đè Equals. Sử dụng base64 đơn giản hơn để có quyền, nhưng hơi kém hiệu quả nếu bạn thực sự chỉ quan tâm đến việc so sánh các giá trị băm.)

Nếu bạn cần biểu diễn hàm băm dưới dạng chuỗi, bạn có thể chuyển đổi nó thành hex bằng cách sử dụng 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
Nếu bạn muốn md5 "chuẩn", bạn có thể làm: returnBitConverter.ToString(md5.ComputeHash(stream)).Replace("-","").ToLower();
aquina

78
MD5 nằm trong System.Security.Cryptography - chỉ để hiển thị thêm thông tin.
Hans

6
@KalaJ: Nếu bạn đang cố gắng phát hiện ra sự giả mạo có chủ ý, thì CRC32 hoàn toàn không phù hợp. Nếu bạn chỉ nói về việc phát hiện lỗi truyền dữ liệu, thì tốt thôi. Cá nhân tôi có thể sử dụng SHA-256 theo thói quen :) Tôi không biết về việc hỗ trợ CRC32 bằng .NET, nhưng bạn có thể tìm kiếm nó nhanh nhất có thể :)
Jon Skeet

12
@aquinas Tôi nghĩ .Replace("-", String.Empty)là một cách tiếp cận tốt hơn. Tôi đã trải qua một phiên gỡ lỗi một giờ vì tôi nhận được kết quả sai khi so sánh đầu vào của người dùng với tệp băm.
fabwu

7
@ wuethrich44, tôi nghĩ rằng vấn đề bạn gặp phải là nếu bạn sao chép / dán mã trong bình luận nguyên văn; Tôi tình cờ nhận thấy điều tương tự. Có hai ký tự vô hình - "không có độ rộng không tham gia" và "không gian chiều rộng bằng không" Unicode - giữa các trích dẫn "trống" trong HTML thô. Tôi không biết nếu đó là trong nhận xét ban đầu hoặc nếu SO là để đổ lỗi ở đây.
Chris Simmons

66

Đây là cách tôi làm điều đó:

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
Tôi ủng hộ bạn vì nhiều người cần phải làm những việc như thế này.
Krythic

6
Tôi nghĩ rằng việc hoán đổi các usingkhối sẽ hữu ích, bởi vì mở một tệp có thể sẽ thất bại. Thất bại phương pháp tiếp cận sớm / nhanh giúp bạn tiết kiệm tài nguyên cần thiết để tạo (và hủy) thể hiện MD5 trong các tình huống như vậy. Ngoài ra, bạn có thể bỏ qua các dấu ngoặc đầu tiên usingvà lưu một mức độ thụt mà không mất khả năng đọc.
Palec

10
Điều này chuyển đổi kết quả dài 16 byte thành chuỗi 16 ký tự, không phải là giá trị hex 32 ký tự dự kiến.
NiKiZe

3
Mã này không tạo ra kết quả mong đợi (kỳ vọng giả định). Đồng ý với @NiKiZe
Nick

1
@Quibbledome, tôi chỉ cố gắng thúc đẩy ý tưởng chung rằng thứ tự lồng nhau của việc sử dụng các câu lệnh là vấn đề. Ở những nơi khác, sự khác biệt có thể là đáng kể. Tại sao không thực hành thói quen phát hiện thất bại sớm? Tuy nhiên, tôi đồng ý rằng trong đoạn trích cụ thể này, thói quen này hầu như không mang lại lợi ích gì.
Palec

7

Tôi biết câu hỏi này đã được trả lời, nhưng đây là những gì tôi sử dụng:

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

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

Có lẽ không phải là cách tốt nhất, nhưng nó có thể có ích.


Tôi đã thực hiện một thay đổi nhỏ cho chức năng GetHash của bạn. Tôi đã biến nó thành một phương thức mở rộng và loại bỏ mã phản chiếu.
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

Điều này thực sự đã làm việc .... cảm ơn bạn!. Tôi đã dành từ lâu để tìm kiếm trực tuyến kết quả sẽ tạo ra chuỗi 32 char md5 bình thường hơn tôi mong đợi. Điều này phức tạp hơn một chút mà tôi muốn nhưng nó chắc chắn hoạt động.
Troubleum

1
@LeslieMarshall nếu bạn định sử dụng nó làm phương thức mở rộng thì bạn nên đặt lại vị trí luồng thay vì để nó ở vị trí cuối
MikeT

3

Đây là một phiên bản đơn giản hơn một chút mà tôi tìm thấy. Nó đọc toàn bộ tập tin trong một lần và chỉ yêu cầu một lệnh duy nhất using.

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

50
Nhược điểm của việc sử dụng ReadAllByteslà nó tải toàn bộ tệp vào một mảng. Điều đó hoàn toàn không hoạt động đối với các tệp lớn hơn 2 GiB và gây áp lực lớn lên GC ngay cả đối với các tệp có kích thước trung bình. Câu trả lời của Jon chỉ phức tạp hơn một chút, nhưng không gặp phải những vấn đề này. Vì vậy, tôi thích câu trả lời của anh ấy hơn của bạn.
CodeInChaos

1
Đặt usingtừng cái một với các dấu ngoặc nhọn đầu tiên using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))cung cấp cho bạn một lần sử dụng trên mỗi dòng mà không cần thụt vào không cần thiết.
NiKiZe

3
@NiKiZe Bạn có thể đặt toàn bộ chương trình trên một dòng và loại bỏ TẤT CẢ vết lõm. Bạn thậm chí có thể sử dụng XYZ làm tên biến! Lợi ích cho người khác là gì?
Derek Johnson

@DerekJohnson điểm mà tôi đang cố gắng thực hiện có lẽ là "và chỉ yêu cầu một usingchỉ thị duy nhất ." không thực sự là một lý do tốt để đọc mọi thứ vào bộ nhớ. Cách tiếp cận hiệu quả hơn là truyền dữ liệu vào ComputeHash, và nếu có thể usingchỉ nên được sử dụng, nhưng tôi hoàn toàn có thể hiểu nếu bạn muốn tránh mức độ thụt lề thêm.
NiKiZe

3

Tôi biết rằng tôi đến dự tiệc muộn nhưng đã thực hiện kiểm tra trước khi thực sự thực hiện giải pháp.

Tôi đã thực hiện kiểm tra đối với lớp MD5 sẵn có và cả md5sum.exe . Trong trường hợp của tôi, lớp inbuilt mất 13 giây trong đó md5sum.exe cũng chỉ khoảng 16-18 giây trong mỗi lần chạy.

    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

Và nếu bạn cần tính toán MD5 để xem liệu nó có khớp với MD5 của blob Azure hay không, thì câu hỏi và câu trả lời SO này có thể hữu ích: hàm băm MD5 được tải lên trên Azure không khớp với cùng một tệp trên máy cục bộ


Nếu bạn nghĩ rằng câu trả lời là không tốt, thì việc bỏ phiếu là tốt. Tuy nhiên, để lại một bình luận mô tả các lý do cho việc downvoate sẽ giúp cải thiện câu trả lời theo thời gian. Bằng cách để lại nhận xét với các đề xuất để cải thiện câu trả lời, bạn có thể đóng góp tốt hơn cho Stack Overflow. Cảm ơn!
Manfred
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.