การสร้างการตรวจสอบ MD5 ของไฟล์


348

มีวิธีง่ายๆในการสร้าง (และตรวจสอบ) MD5 checksums ของรายการไฟล์ใน Python หรือไม่? (ฉันมีโปรแกรมขนาดเล็กที่ฉันกำลังทำงานอยู่และฉันต้องการยืนยันผลรวมของไฟล์)


3
ทำไมไม่ใช้เพียงmd5sum?
kennytm

99
การเก็บไว้ใน Python ช่วยให้จัดการความเข้ากันได้ข้ามแพลตฟอร์มได้ง่ายขึ้น
Alexander

หากคุณต้องการโซลูชันที่มี "แถบความคืบหน้า * หรือที่คล้ายกัน (สำหรับไฟล์ที่มีขนาดใหญ่มาก) ให้พิจารณาโซลูชันนี้: stackoverflow.com/questions/1131220//
Laurent LAPORTE

1
@kennytm ลิงค์ที่คุณให้มาพูดแบบนี้ในวรรคที่สอง: "พื้นฐาน MD5 อัลกอริทึมจะไม่ถือว่าเชื่อถือได้" md5sumในขณะที่การอธิบาย นั่นคือเหตุผลที่โปรแกรมเมอร์ที่ใส่ใจเรื่องความปลอดภัยไม่ควรใช้มันในความคิดของฉัน
Debug255

1
@ Debug255 จุดที่ดีและถูกต้อง ทั้งคู่md5sumและเทคนิคที่อธิบายไว้ในคำถาม SO นี้ควรหลีกเลี่ยง - จะดีกว่าถ้าใช้ SHA-2 หรือ SHA-3 ถ้าเป็นไปได้: en.wikipedia.org/wiki/Secure_Hash_Algorithms
ต่อ Lundberg

คำตอบ:


464

คุณสามารถใช้hashlib.md5 ()

โปรดทราบว่าบางครั้งคุณอาจไม่สามารถใส่ไฟล์ทั้งหมดในหน่วยความจำได้ ในกรณีดังกล่าวคุณจะต้องอ่านชิ้นข้อมูล 4096 ไบต์ตามลำดับและป้อนไปยังmd5วิธีการ:

import hashlib
def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

หมายเหตุ: hash_md5.hexdigest()จะคืนค่าการแทนสตริงฐานสิบหกสำหรับสรุปย่อยหากคุณต้องการใช้ไบต์ที่บีบอัดไว้return hash_md5.digest()ดังนั้นคุณไม่จำเป็นต้องแปลงกลับ


297

มีวิธีที่ไม่มีประสิทธิภาพของหน่วยความจำสวยที่ไม่มีประสิทธิภาพ

ไฟล์เดียว:

import hashlib
def file_as_bytes(file):
    with file:
        return file.read()

print hashlib.md5(file_as_bytes(open(full_path, 'rb'))).hexdigest()

รายการไฟล์:

[(fname, hashlib.md5(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

จำไว้ว่าMD5 นั้นใช้งานไม่ได้และไม่ควรใช้เพื่อวัตถุประสงค์ใด ๆ เนื่องจากการวิเคราะห์ช่องโหว่อาจยุ่งยากมากและการวิเคราะห์การใช้งานในอนาคตที่เป็นไปได้รหัสของคุณอาจถูกนำไปใช้เพื่อความปลอดภัย IMHO ควรลบออกจากไลบรารีดังนั้นทุกคนที่ใช้จะถูกบังคับให้อัปเดต ดังนั้นนี่คือสิ่งที่คุณควรทำแทน:

[(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

หากคุณต้องการเพียง 128 .digest()[:16]บิตมูลค่าย่อยที่คุณสามารถทำได้

สิ่งนี้จะให้รายการของ tuples แต่ละ tuple มีชื่อไฟล์และ hash ของมัน

ฉันขอให้คุณใช้ MD5 อีกครั้ง อย่างน้อยคุณควรใช้ SHA1 และพบข้อบกพร่องล่าสุดใน SHA1ซึ่งอาจไม่ใช่สิ่งนั้น บางคนคิดว่าตราบใดที่คุณไม่ได้ใช้ MD5 เพื่อจุดประสงค์ 'เข้ารหัส' คุณก็ไม่เป็นไร แต่สิ่งต่าง ๆ มีแนวโน้มที่จะจบลงด้วยขอบเขตที่กว้างกว่าที่คุณคาดไว้ในตอนแรกและการวิเคราะห์ช่องโหว่ชั่วคราวของคุณอาจพิสูจน์ว่ามีข้อบกพร่องอย่างสมบูรณ์ เป็นการดีที่สุดที่คุณจะต้องติดนิสัยการใช้อัลกอริทึมที่ถูกต้องจากประตู มันเป็นเพียงการพิมพ์พวงของตัวอักษรที่แตกต่างกันทั้งหมด มันไม่ยากเลย

นี่เป็นวิธีที่ซับซ้อนกว่า แต่มีประสิทธิภาพของหน่วยความจำ :

import hashlib

def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
    for block in bytesiter:
        hasher.update(block)
    return hasher.hexdigest() if ashexstr else hasher.digest()

def file_as_blockiter(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block
            block = afile.read(blocksize)


[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.md5()))
    for fname in fnamelst]

และอีกครั้งเนื่องจาก MD5 เสียและไม่ควรใช้อีกต่อไปจริงๆ:

[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
    for fname in fnamelst]

อีกครั้งคุณสามารถวาง[:16]สายหลังจากนั้นhash_bytestr_iter(...)ถ้าคุณต้องการย่อย 128 บิตที่คุ้มค่า


66
ฉันแค่ใช้ MD5 เพื่อยืนยันว่าไฟล์ไม่เสียหาย ฉันไม่ได้กังวลเกี่ยวกับมันจะถูกทำลาย
Alexander

87
@TheLifelessOne: และแม้จะมีคำเตือนที่น่ากลัว @Omnifarious นั่นก็คือการใช้ MD5 อย่างสมบูรณ์แบบ
ประธานาธิบดี James K. Polk

22
@GregS, @TheLifelessOne - ใช่และสิ่งต่อไปที่คุณรู้ว่ามีคนค้นพบวิธีที่จะใช้ข้อเท็จจริงนี้เกี่ยวกับแอปพลิเคชันของคุณเพื่อทำให้ไฟล์ได้รับการยอมรับว่าไม่เสียหายเมื่อไม่ใช่ไฟล์ที่คุณคาดหวังเลย ไม่ฉันยืนตามคำเตือนที่น่ากลัว ฉันคิดว่า MD5 ควรถูกลบออกหรือมีคำเตือนเรื่องเลิกใช้งาน
Omnifarious

10
ฉันอาจใช้. hexdigest () แทน. digest () - มันง่ายสำหรับมนุษย์ที่จะอ่าน - ซึ่งเป็นจุดประสงค์ของ OP
zbstof

21
ฉันใช้วิธีแก้ปัญหานี้ แต่ให้แฮชเดียวกันกับไฟล์ PDF สองไฟล์ที่ไม่ถูกต้อง วิธีแก้ไขคือการเปิดไฟล์โดยระบุโหมดไบนารีนั่นคือ: ((fname, hashlib.md5 (open (fname, 'rb' ) .read ()). hexdigest ()) สำหรับ fname ใน fnamelst] นี่เกี่ยวข้องมากขึ้น ฟังก์ชั่นเปิดมากกว่า md5 แต่ฉันคิดว่ามันอาจจะมีประโยชน์ที่จะรายงานว่ามันได้รับข้อกำหนดสำหรับความเข้ากันได้ข้ามแพลตฟอร์มที่ระบุข้างต้น (ดูเพิ่มเติมที่: docs.python.org/2/tutorial/ ...... )
BlueCoder

34

เห็นได้ชัดว่าฉันไม่ได้เพิ่มอะไรใหม่พื้นฐาน แต่เพิ่มคำตอบนี้ก่อนที่ฉันจะขึ้นแสดงความคิดเห็นสถานะรวมทั้งภูมิภาครหัสทำให้สิ่งที่ชัดเจนมากขึ้น - ต่อไปโดยเฉพาะเพื่อตอบคำถามของ @ Nemo จากคำตอบของ Omnifarious:

ฉันคิดว่าจะลองเช็คซัมเล็กน้อย (มาที่นี่เพื่อหาคำแนะนำเกี่ยวกับขนาดบล็อกโดยเฉพาะ) และพบว่าวิธีนี้อาจเร็วกว่าที่คุณคาดไว้ ใช้วิธีที่เร็วที่สุด (แต่ค่อนข้างธรรมดา) timeit.timeitหรือเป็น/usr/bin/timeผลมาจากวิธีการตรวจสอบแต่ละวิธีของไฟล์ประมาณ 11MB:

$ ./sum_methods.py
crc32_mmap(filename) 0.0241742134094
crc32_read(filename) 0.0219960212708
subprocess.check_output(['cksum', filename]) 0.0553209781647
md5sum_mmap(filename) 0.0286180973053
md5sum_read(filename) 0.0311000347137
subprocess.check_output(['md5sum', filename]) 0.0332629680634
$ time md5sum /tmp/test.data.300k
d3fe3d5d4c2460b5daacc30c6efbc77f  /tmp/test.data.300k

real    0m0.043s
user    0m0.032s
sys     0m0.010s
$ stat -c '%s' /tmp/test.data.300k
11890400

ดังนั้นดูเหมือนว่าทั้ง Python และ / usr / bin / md5sum ใช้เวลาประมาณ 30ms สำหรับไฟล์ 11MB md5sumฟังก์ชั่นที่เกี่ยวข้อง( md5sum_readในรายการด้านบน) ค่อนข้างคล้ายกับ Omnifarious's:

import hashlib
def md5sum(filename, blocksize=65536):
    hash = hashlib.md5()
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            hash.update(block)
    return hash.hexdigest()

จริงอยู่สิ่งเหล่านี้มาจากการวิ่งครั้งเดียว ( mmapอันที่เป็น smidge เร็วกว่าเสมอเมื่อมีการวิ่งอย่างน้อยสองสามโหล) และของฉันมักจะได้รับการเพิ่มf.read(blocksize)หลังจากบัฟเฟอร์หมด แต่มันสามารถทำซ้ำได้อย่างสมเหตุสมผลและแสดงว่าmd5sumบนบรรทัดคำสั่งคือ ไม่จำเป็นต้องเร็วกว่าการใช้ Python ...

แก้ไข: ขออภัยสำหรับความล่าช้านานไม่ได้มองเรื่องนี้ในบางเวลา แต่เพื่อตอบคำถามของ @ EdRandall ฉันจะเขียนการติดตั้ง Adler32 อย่างไรก็ตามฉันไม่ได้ใช้มาตรฐาน โดยพื้นฐานแล้วจะเหมือนกับ CRC32: แทนที่จะเป็น init, update, และ digest call ทุกอย่างเป็นการzlib.adler32()โทร:

import zlib
def adler32sum(filename, blocksize=65536):
    checksum = zlib.adler32("")
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            checksum = zlib.adler32(block, checksum)
    return checksum & 0xffffffff

โปรดทราบว่าสิ่งนี้จะต้องเริ่มต้นด้วยสตริงว่างเปล่าเนื่องจากผลรวมของ Adler แตกต่างกันอย่างแน่นอนเมื่อเริ่มต้นจากศูนย์เมื่อเทียบกับผลรวมของพวกเขา""ซึ่งก็คือ1- CRC สามารถเริ่มต้นด้วย0แทน ANDไอเอ็นจีเป็นสิ่งจำเป็นที่จะทำให้มันเป็น 32 บิตจำนวนเต็มไม่ได้ลงนามซึ่งทำให้มั่นใจได้ว่ามันส่งกลับค่าเดียวกันในรุ่นหลาม


คุณสามารถเพิ่มสองบรรทัดเปรียบเทียบ SHA1 และ zlib.adler32 ได้หรือไม่
Ed Randall

1
ฟังก์ชัน md5sum () ด้านบนถือว่าคุณมีสิทธิ์ในการเขียนไฟล์ หากคุณแทนที่ "r + b" ในการเปิด () ด้วย "rb" มันจะทำงานได้ดี
Kevin Lyda

1
@EdRandall: adler32 ไม่คุ้มกับการรบกวนเช่น leviathansecurity.com/blog/analysis-of-adler32
MikeW

6

ใน Python 3.8+ คุณสามารถทำได้

import hashlib
with open("your_filename.txt", "rb") as f:
    file_hash = hashlib.md5()
    while chunk := f.read(8192):
        file_hash.update(chunk)

print(file_hash.digest())
print(file_hash.hexdigest())  # to get a printable str instead of bytes

ลองใช้hashlib.blake2bแทนmd5(เพียงแทนที่md5ด้วยblake2bในข้อมูลโค้ดด้านบน) มันปลอดภัยและเข้ารหัสเร็วกว่า MD5


:=ผู้ประกอบการเป็น "ผู้ประกอบการที่ได้รับมอบหมาย" (ใหม่เพื่อหลาม 3.8+); มันช่วยให้คุณกำหนดค่าภายในนิพจน์ที่ใหญ่กว่า ข้อมูลเพิ่มเติมที่นี่: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Benjamin

0
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()

3
Hi! โปรดเพิ่มคำอธิบายลงในรหัสของคุณเพื่อดูว่าทำไมนี่จึงเป็นวิธีแก้ปัญหา นอกจากนี้โพสต์นี้ค่อนข้างเก่าดังนั้นคุณควรเพิ่มข้อมูลบางอย่างว่าทำไมโซลูชันของคุณจึงเพิ่มสิ่งที่คนอื่นไม่ได้กล่าวถึง
d_kennetz

1
มันเป็นวิธีที่ไม่มีประสิทธิภาพอีกความทรงจำ
Erik Aronesty

-2

ฉันคิดว่าการใช้แพ็คเกจที่เรียกใช้และ md5sum binary นั้นสะดวกกว่า subprocess หรือแพ็คเกจ md5 เล็กน้อย

import invoke

def get_file_hash(path):

    return invoke.Context().run("md5sum {}".format(path), hide=True).stdout.split(" ")[0]

แน่นอนว่าคุณได้เรียกใช้และติดตั้ง md5sum แล้ว


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