Menghasilkan checksum MD5 file

348

Apakah ada cara sederhana untuk menghasilkan (dan memeriksa) checksum MD5 dari daftar file dengan Python? (Saya punya program kecil yang sedang saya kerjakan, dan saya ingin mengkonfirmasi checksum dari file).

Alexander
sumber
3
Kenapa tidak pakai saja md5sum?
kennytm
99
Menyimpannya dalam Python membuatnya lebih mudah untuk mengelola kompatibilitas lintas platform.
Alexander
Jika Anda menginginkan solusi dengan "progress bar * atau yang serupa (untuk file yang sangat besar), pertimbangkan solusi ini: stackoverflow.com/questions/1131220/…
Laurent LAPORTE
1
@kennytm Tautan yang Anda berikan mengatakan ini di paragraf kedua: "Algoritma MD5 yang mendasarinya tidak lagi dianggap aman" saat menjelaskan md5sum. Itulah mengapa programmer yang sadar akan keamanan sebaiknya tidak menggunakannya menurut saya.
Debug255
1
@ Debug255 Poin bagus dan valid. Baik md5sumdan teknik yang dijelaskan dalam pertanyaan SO ini harus dihindari - lebih baik menggunakan SHA-2 atau SHA-3, jika mungkin: en.wikipedia.org/wiki/Secure_Hash_Algorithms
Per Lundberg

Jawaban:

462

Anda dapat menggunakan hashlib.md5 ()

Perhatikan bahwa terkadang Anda tidak dapat memasukkan seluruh file ke dalam memori. Dalam hal ini, Anda harus membaca potongan 4096 byte secara berurutan dan mengumpankannya ke md5metode:

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

Catatan: hash_md5.hexdigest() akan mengembalikan representasi string hex untuk digest, jika Anda hanya perlu menggunakan byte yang dikemas return hash_md5.digest(), sehingga Anda tidak perlu mengkonversi kembali.

quantumSoup
sumber
297

Ada cara yang cukup tidak efisien memori .

satu file:

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

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

daftar file:

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

Ingat, MD5 diketahui rusak dan tidak boleh digunakan untuk tujuan apa pun karena analisis kerentanan bisa sangat rumit, dan menganalisis kemungkinan penggunaan di masa depan yang mungkin dilakukan oleh kode Anda untuk masalah keamanan tidak mungkin. IMHO, itu harus dihapus dari perpustakaan sehingga semua orang yang menggunakannya terpaksa memperbarui. Jadi, inilah yang harus Anda lakukan sebagai gantinya:

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

Jika Anda hanya ingin mencerna 128 bit, Anda bisa melakukannya .digest()[:16].

Ini akan memberi Anda daftar tupel, masing-masing tupel berisi nama file dan hash-nya.

Sekali lagi saya sangat mempertanyakan penggunaan MD5 Anda. Anda harus setidaknya menggunakan SHA1, dan diberi kekurangan baru-baru ini ditemukan di SHA1 , bahkan mungkin tidak. Beberapa orang berpikir bahwa selama Anda tidak menggunakan MD5 untuk tujuan 'kriptografi', Anda baik-baik saja. Tetapi hal-hal memiliki kecenderungan untuk menjadi lebih luas dalam ruang lingkup daripada yang Anda harapkan, dan analisis kerentanan kasual Anda mungkin terbukti benar-benar cacat. Yang terbaik adalah membiasakan diri menggunakan algoritma yang tepat di luar gerbang. Itu hanya mengetik banyak huruf yang berbeda. Tidak sesulit itu.

Berikut ini cara yang lebih kompleks, tetapi hemat memori :

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]

Dan, sekali lagi, karena MD5 rusak dan seharusnya tidak pernah benar-benar digunakan lagi:

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

Sekali lagi, Anda dapat melakukan [:16]panggilan setelah ke hash_bytestr_iter(...)jika Anda hanya ingin mencerna 128 bit.

Beraneka ragam
sumber
66
Saya hanya menggunakan MD5 untuk mengkonfirmasi file tidak rusak. Saya tidak begitu khawatir tentang itu rusak.
Alexander
87
@TheLifelessOne: Dan meskipun peringatan menakutkan @Omnifarious, itu adalah penggunaan MD5 yang sangat baik.
Presiden James K. Polk
22
@Regs, @TheLifelessOne - Ya, dan selanjutnya Anda tahu seseorang menemukan cara untuk menggunakan fakta ini tentang aplikasi Anda untuk menyebabkan file diterima sebagai tidak rusak ketika itu bukan file yang Anda harapkan sama sekali. Tidak, saya mendukung peringatan menakutkan saya. Saya pikir MD5 harus dihapus atau datang dengan peringatan penghentian.
Mahakuasa
10
Saya mungkin akan menggunakan .hexdigest () daripada .digest () - lebih mudah bagi manusia untuk membaca - yang merupakan tujuan OP.
zbstof
21
Saya menggunakan solusi ini tetapi tidak benar memberikan hash yang sama untuk dua file pdf yang berbeda. Solusinya adalah membuka file dengan menetapkan mode biner, yaitu: [(fname, hashlib.md5 (buka (fname, 'rb' ) .read ()). Hexdigest ()). Hexdigest ()) untuk fname di fnamelst] Ini lebih terkait untuk fungsi terbuka daripada md5 tapi saya pikir mungkin berguna untuk melaporkannya mengingat persyaratan untuk kompatibilitas lintas-platform yang dinyatakan di atas (lihat juga: docs.python.org/2/tutorial/… ).
BlueCoder
34

Saya jelas tidak menambahkan sesuatu yang pada dasarnya baru, tetapi menambahkan jawaban ini sebelum saya mengomentari status, ditambah wilayah kode membuat segalanya lebih jelas - lagi pula, khususnya untuk menjawab pertanyaan @ Nemo dari jawaban Omnifarious:

Saya kebetulan berpikir tentang checksum sedikit (datang ke sini mencari saran tentang ukuran blok, khususnya), dan telah menemukan bahwa metode ini mungkin lebih cepat daripada yang Anda harapkan. Mengambil yang tercepat (tetapi sangat khas) timeit.timeitatau /usr/bin/timehasil dari masing-masing dari beberapa metode checksumming file kira-kira. 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

Jadi, sepertinya Python dan / usr / bin / md5sum membutuhkan sekitar 30ms untuk file 11MB. Fungsi yang relevan md5sum( md5sum_readdalam daftar di atas) sangat mirip dengan Omnifarious:

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

Memang, ini dari sekali jalan ( mmapyang selalu smidge lebih cepat ketika setidaknya beberapa selusin dibuat), dan milikku biasanya mendapat tambahan f.read(blocksize)setelah buffer habis, tapi itu cukup berulang dan menunjukkan bahwa md5sumpada baris perintah belum tentu lebih cepat dari implementasi Python ...

EDIT: Maaf atas keterlambatan yang lama, belum melihat ini dalam beberapa waktu, tetapi untuk menjawab pertanyaan @ EdRandall, saya akan menuliskan implementasi Adler32. Namun, saya belum menjalankan tolok ukur untuk itu. Pada dasarnya sama dengan CRC32: alih-alih init, memperbarui, dan mencerna panggilan, semuanya adalah zlib.adler32()panggilan:

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

Perhatikan bahwa ini harus dimulai dengan string kosong, karena jumlah Adler memang berbeda ketika mulai dari nol dibandingkan jumlah mereka "", yaitu 1- CRC dapat memulai dengan 0sebagai gantinya. The AND-ing diperlukan untuk membuat 32-bit unsigned integer, yang menjamin ia mengembalikan nilai yang sama di versi Python.

rsandwick3
sumber
Bisakah Anda menambahkan beberapa baris membandingkan SHA1, dan juga zlib.adler32 mungkin?
Ed Randall
1
Fungsi md5sum () di atas mengasumsikan Anda memiliki akses tulis ke file. Jika Anda mengganti "r + b" di panggilan terbuka () dengan "rb" itu akan berfungsi dengan baik.
Kevin Lyda
1
@ EdRandall: adler32 benar-benar tidak layak untuk diganggu, misalnya. leviathansecurity.com/blog/analysis-of-adler32
MikeW
6

Dengan Python 3.8+ Anda bisa melakukannya

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

Pertimbangkan untuk menggunakan hashlib.blake2balih-alih md5(ganti saja md5dengan blake2bcuplikan di atas). Ini aman secara kriptografis dan lebih cepat dari MD5.

Boris
sumber
The :=Operator adalah "tugas operator" (baru ke Python 3.8+); ini memungkinkan Anda untuk menetapkan nilai di dalam ekspresi yang lebih besar; info lebih lanjut di sini: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Benjamin
0
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()
Johnson
sumber
3
Hai! Harap tambahkan beberapa penjelasan ke kode Anda mengapa ini merupakan solusi untuk masalah ini. Selain itu, postingan ini sudah cukup tua, jadi Anda juga harus menambahkan beberapa informasi tentang mengapa solusi Anda menambahkan sesuatu yang belum ditangani orang lain.
d_kennetz
1
Ini cara memori lain yang tidak efisien
Erik Aronesty
-2

Saya pikir mengandalkan paket invoke dan md5sum binary sedikit lebih nyaman daripada paket subprocess atau md5

import invoke

def get_file_hash(path):

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

Ini tentu saja mengasumsikan Anda telah memohon dan md5sum diinstal.

Puchatek
sumber
3
Jika pathadalah jalur yang disediakan pengguna, ini akan memungkinkan setiap pengguna menjalankan perintah bash sewenang-wenang di sistem Anda.
Boris