คำนวณ MD5 checksum สำหรับไฟล์


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 นั้นง่ายกว่าในการรับที่ถูกต้อง แต่มีประสิทธิภาพน้อยลงหากคุณสนใจเพียงเปรียบเทียบแฮชเท่านั้น)

หากคุณต้องการเป็นตัวแทนของแฮชเป็นสตริงคุณสามารถแปลงเป็น hex ได้โดยใช้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
@quinas ฉันคิดว่า.Replace("-", String.Empty)เป็นวิธีที่ดีกว่า ฉันผ่านเซสชันการดีบักหนึ่งชั่วโมงเพราะฉันได้รับผลลัพธ์ที่ไม่ถูกต้องเมื่อเปรียบเทียบอินพุตของผู้ใช้กับแฮชของไฟล์
fabwu

7
@ wuethrich44 ฉันคิดว่าปัญหาที่คุณมีคือถ้าคุณคัดลอก / วางรหัสในคำพูดแสดงความคิดเห็น aquinas; ฉันบังเอิญสังเกตสิ่งเดียวกัน มีอักขระที่มองไม่เห็นสองตัวคือ "zero-width non-joiner" และ Unicode "zero width space" - ระหว่างเครื่องหมายคำพูด "empty" ใน HTML แบบ raw ฉันไม่รู้ว่ามันอยู่ในความคิดเห็นดั้งเดิมหรือถ้าเป็นเช่นนั้นจะตำหนิที่นี่
Chris Simmons

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
Nick

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 ปกติมากกว่าที่คาดไว้ มันซับซ้อนกว่านี้นิดหน่อยที่ฉันจะชอบ แต่มันก็ใช้งานได้ดี
ปัญหา

1
@LeslieMarshall หากคุณจะใช้เป็นวิธีการขยายคุณควรรีเซ็ตตำแหน่งสตรีมแทนที่จะปล่อยไว้ที่ตำแหน่งสิ้นสุด
MikeT

3

นี่คือรุ่นที่เรียบง่ายกว่าเล็กน้อยที่ฉันพบ มันอ่านไฟล์ทั้งหมดในครั้งเดียวและต้องการเพียงusingคำสั่งเดียว

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

50
ข้อเสียของการใช้ReadAllBytesคือโหลดไฟล์ทั้งหมดลงในอาร์เรย์เดียว ไม่สามารถใช้งานได้กับไฟล์ที่มีขนาดใหญ่กว่า 2 GiB และสร้างแรงกดดันต่อ GC ได้แม้กระทั่งสำหรับไฟล์ขนาดกลาง คำตอบของจอนนั้นซับซ้อนกว่าเล็กน้อยเพียงเล็กน้อย แต่ไม่ได้รับปัญหาเหล่านี้ ดังนั้นฉันชอบคำตอบของเขามากกว่าคุณ
CodesInChaos

1
ใส่ในusings หลังจากแต่ละอื่น ๆusing (var md5 = MD5.Create()) using (var stream = File.OpenRead(filename))โดยไม่ต้องวงเล็บปีกกาแรกให้คุณใช้ต่อบรรทัดโดยไม่มีการเยื้องที่ไม่จำเป็น
NiKiZe

3
@NiKiZe คุณสามารถใส่โปรแกรมทั้งหมดในบรรทัดเดียวและกำจัดการเยื้องทั้งหมด คุณสามารถใช้ XYZ เป็นชื่อตัวแปรได้! ประโยชน์ต่อผู้อื่นคืออะไร?
Derek Johnson

@ ดีเร็กจอห์นสันจุดที่ฉันพยายามทำอาจเป็นไปได้ว่า "และต้องการเพียงusingคำสั่งเดียว" ไม่ใช่เหตุผลที่ดีที่จะอ่านทุกอย่างในความทรงจำ วิธีที่มีประสิทธิภาพมากขึ้นคือการสตรีมข้อมูลลงในComputeHashและusingควรใช้ แต่ถ้าเป็นไปได้แต่ฉันสามารถเข้าใจได้อย่างสมบูรณ์ถ้าคุณต้องการหลีกเลี่ยงการเยื้องระดับพิเศษ
NiKiZe

3

ฉันรู้ว่าฉันมาสาย แต่ได้ทำการทดสอบก่อนที่จะใช้โซลูชันจริง

ฉันทำการทดสอบกับคลาส MD5 inbuilt และmd5sum.exeด้วย ในกรณีของฉันคลาส inbuilt ใช้เวลา 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 blob ดังนั้นคำถามและคำตอบ SO นี้อาจเป็นประโยชน์: MD5 hash ของ blob ที่อัปโหลดบน Azure ไม่ตรงกับไฟล์เดียวกันบนเครื่องท้องถิ่น


หากคุณคิดว่าคำตอบนั้นไม่ดีการลงคะแนนถือว่าไม่เป็นผล อย่างไรก็ตามการออกความคิดเห็นที่อธิบายถึงสาเหตุของการดาวน์โหลตจะช่วยปรับปรุงคำตอบเมื่อเวลาผ่านไป โดยการแสดงความคิดเห็นพร้อมข้อเสนอแนะเพื่อปรับปรุงคำตอบคุณจะสามารถมีส่วนร่วมใน Stack Overflow ได้ดียิ่งขึ้น ขอบคุณ!
Manfred
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.