Tạo tổng kiểm MD5 của tệp


348

Có cách nào đơn giản để tạo (và kiểm tra) tổng kiểm MD5 của danh sách các tệp trong Python không? (Tôi có một chương trình nhỏ mà tôi đang làm việc và tôi muốn xác nhận tổng kiểm tra của các tệp).


3
Tại sao không chỉ sử dụng md5sum?
kennytm

99
Giữ nó trong Python giúp quản lý khả năng tương thích đa nền tảng dễ dàng hơn.
Alexander

Nếu bạn muốn giải pháp với "thanh tiến trình * hoặc tương tự (đối với các tệp rất lớn), hãy xem xét giải pháp này: stackoverflow.com/questions/1131220/ Thẻ
Laurent LAPORTE

1
@kennytm Liên kết bạn cung cấp cho biết điều này trong đoạn thứ hai: "Thuật toán MD5 cơ bản không còn được coi là an toàn" trong khi mô tả md5sum. Đó là lý do tại sao các lập trình viên có ý thức bảo mật không nên sử dụng nó theo ý kiến ​​của tôi.
Debug255

1
@ Debug255 Điểm tốt và hợp lệ. md5sumNên tránh cả hai và kỹ thuật được mô tả trong câu hỏi SO này - tốt hơn là sử dụng SHA-2 hoặc SHA-3, nếu có thể: en.wikipedia.org/wiki/Secure_Hash_Alacticms
Per Lundberg

Câu trả lời:


462

Bạn có thể sử dụng hashlib.md5 ()

Lưu ý rằng đôi khi bạn sẽ không thể vừa toàn bộ tệp trong bộ nhớ. Trong trường hợp đó, bạn sẽ phải đọc các chuỗi 4096 byte theo tuần tự và đưa chúng vào md5phương thức:

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

Lưu ý: hash_md5.hexdigest() sẽ trả về biểu diễn chuỗi hex cho thông báo, nếu bạn chỉ cần sử dụng byte được đóng gói return hash_md5.digest(), vì vậy bạn không phải chuyển đổi lại.


297

Có một cách mà bộ nhớ khá kém hiệu quả .

tập tin duy nhất:

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

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

danh sách các tập tin:

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

Mặc dù vậy, hãy nhớ lại rằng MD5 đã bị hỏng và không nên được sử dụng cho bất kỳ mục đích nào vì phân tích lỗ hổng có thể rất khó khăn và việc phân tích bất kỳ việc sử dụng nào có thể xảy ra trong tương lai mà mã của bạn có thể được đưa ra cho các vấn đề bảo mật là không thể. IMHO, nó nên được gỡ bỏ khỏi thư viện để mọi người sử dụng nó buộc phải cập nhật. Vì vậy, đây là những gì bạn nên làm thay thế:

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

Nếu bạn chỉ muốn tiêu hóa 128 bit, bạn có thể làm .digest()[:16].

Điều này sẽ cung cấp cho bạn một danh sách các bộ dữ liệu, mỗi bộ chứa tên của tệp và hàm băm của nó.

Một lần nữa tôi mạnh mẽ đặt câu hỏi về việc bạn sử dụng MD5. Bạn ít nhất nên sử dụng SHA1, và đưa ra các lỗ hổng gần đây được phát hiện trong SHA1 , thậm chí có thể không phải như vậy. Một số người nghĩ rằng miễn là bạn không sử dụng MD5 cho mục đích 'mật mã', bạn vẫn ổn. Nhưng công cụ có xu hướng kết thúc phạm vi rộng hơn bạn mong đợi ban đầu, và phân tích lỗ hổng thông thường của bạn có thể chứng minh hoàn toàn thiếu sót. Tốt nhất là hãy tập thói quen sử dụng thuật toán phù hợp ngoài cổng. Chỉ cần gõ một loạt các chữ cái khác nhau là được. Nó không khó lắm đâu.

Đây là một cách phức tạp hơn, nhưng hiệu quả bộ nhớ :

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]

Và, một lần nữa, vì MD5 bị hỏng và không thực sự được sử dụng nữa:

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

Một lần nữa, bạn có thể đặt [:16]sau cuộc gọi đến hash_bytestr_iter(...)nếu bạn chỉ muốn tiêu hóa 128 bit.


66
Tôi chỉ sử dụng MD5 để xác nhận tệp không bị hỏng. Tôi không quá quan tâm đến việc nó bị phá vỡ.
Alexander

87
@TheLiflessOne: Và mặc dù cảnh báo đáng sợ @Omnifarious, đó là cách sử dụng MD5 hoàn toàn tốt.
Tổng thống James K. Polk

22
@GregS, @TheLiflessOne - Vâng, và điều tiếp theo bạn biết ai đó tìm ra cách sử dụng thực tế này về ứng dụng của bạn để khiến một tệp được chấp nhận là không bị lỗi khi đó không phải là tệp bạn mong đợi. Không, tôi đứng trước những cảnh báo đáng sợ của tôi. Tôi nghĩ MD5 nên được gỡ bỏ hoặc đi kèm với các cảnh báo không dùng nữa.
Omnifarious

10
Có lẽ tôi sẽ sử dụng .hexdigest () thay vì .digest () - con người dễ đọc hơn - đó là mục đích của OP.
zbstof

21
Tôi đã sử dụng giải pháp này nhưng nó đã đưa ra cùng một hàm băm cho hai tệp pdf khác nhau. Giải pháp là mở các tệp bằng cách chỉ định chế độ nhị phân, đó là: [(fname, hashlib.md5 (open (fname, 'rb' ) .read ()). Hexdigest ()) cho fname trong fnamelst] Điều này có liên quan nhiều hơn với chức năng mở hơn md5 nhưng tôi nghĩ có thể hữu ích khi báo cáo nó đưa ra yêu cầu về khả năng tương thích đa nền tảng đã nêu ở trên (xem thêm: docs.python.org/2/tutorial/ trộm ).
BlueCoder

34

Tôi rõ ràng không thêm bất cứ điều gì mới về cơ bản, nhưng đã thêm câu trả lời này trước khi tôi nhận được trạng thái bình luận, cộng với các vùng mã làm cho mọi thứ rõ ràng hơn - dù sao, cụ thể là trả lời câu hỏi của @ Nemo từ câu trả lời của Omnifarious:

Tôi tình cờ nghĩ về tổng kiểm tra một chút (cụ thể là đến đây để tìm gợi ý về kích thước khối, và đã thấy rằng phương pháp này có thể nhanh hơn bạn mong đợi. Lấy nhanh nhất (nhưng khá điển hình) timeit.timeithoặc /usr/bin/timekết quả từ mỗi một số phương pháp kiểm tra một tệp xấp xỉ. 11 MB:

$ ./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

Vì vậy, có vẻ như cả Python và / usr / bin / md5sum mất khoảng 30ms cho một tệp 11MB. Hàm liên quan md5sum( md5sum_readtrong danh sách trên) khá giống với 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()

Cấp, đây là từ các lần chạy đơn lẻ ( mmapnhững lần chạy luôn nhanh hơn khi có ít nhất vài chục lần chạy) và tôi thường có thêm một lần nữa f.read(blocksize)sau khi bộ đệm bị cạn kiệt, nhưng nó có thể lặp lại một cách hợp lý và cho thấy rằng md5sumtrên dòng lệnh là không nhất thiết phải nhanh hơn triển khai Python ...

EDIT: Xin lỗi vì sự chậm trễ lâu, đã không xem xét điều này trong một thời gian, nhưng để trả lời câu hỏi của @ EdRandall, tôi sẽ viết ra một triển khai Adler32. Tuy nhiên, tôi đã không chạy các tiêu chuẩn cho nó. Về cơ bản, nó giống như CRC32: thay vì các cuộc gọi init, cập nhật và tiêu hóa, mọi thứ đều là một zlib.adler32()cuộc gọi:

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

Lưu ý rằng điều này phải bắt đầu bằng chuỗi rỗng, vì tổng Adler thực sự khác nhau khi bắt đầu từ số 0 so với tổng của chúng "", đó là 1- CRC có thể bắt đầu bằng 0thay thế. Việc AND-ing là cần thiết để biến nó thành một số nguyên không dấu 32 bit, đảm bảo nó trả về cùng một giá trị trên các phiên bản Python.


Bạn có thể thêm một vài dòng so sánh SHA1 và zlib.adler32 không?
Ed Randall

1
Hàm md5sum () ở trên giả định rằng bạn có quyền ghi vào tệp. Nếu bạn thay thế "r + b" trong cuộc gọi open () bằng "rb" thì nó sẽ hoạt động tốt.
Kevin Lyda

1
@EdRandall: adler32 thực sự không đáng bận tâm, vd. leviathansecurity.com/blog/analysis-of-adler32
MikeW

6

Trong Python 3.8+ bạn có thể làm

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

Cân nhắc sử dụng hashlib.blake2bthay vì md5(chỉ thay thế md5bằng blake2btrong đoạn trích trên). Nó bảo mật bằng mật mã và nhanh hơn MD5.


Các :=nhà điều hành là một "toán tử gán" (mới để Python 3.8+); nó cho phép bạn gán các giá trị bên trong một biểu thức lớn hơn; thêm thông tin ở đây: docs.python.org/3/whatsnew/3.8.html#assocate-expressions
Benjamin

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

3
Chào! Vui lòng thêm một số giải thích cho mã của bạn về lý do tại sao đây là một giải pháp cho vấn đề. Hơn nữa, bài đăng này khá cũ, vì vậy bạn cũng nên thêm một số thông tin về lý do giải pháp của bạn thêm một cái gì đó mà những người khác chưa giải quyết.
d_kennetz

1
Đó là một cách khác bộ nhớ không hiệu quả
Erik Aronesty

-2

Tôi nghĩ rằng việc dựa vào gói invoke và md5sum binary sẽ thuận tiện hơn một chút so với gói con hoặc gói md5

import invoke

def get_file_hash(path):

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

Điều này tất nhiên giả sử bạn đã gọi và cài đặt md5sum.


3
Nếu pathlà đường dẫn do người dùng cung cấp, điều này sẽ cho phép bất kỳ người dùng nào thực thi các lệnh bash tùy ý trên hệ thống của bạn.
Boris
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.