Generieren einer MD5-Prüfsumme einer Datei

348

Gibt es eine einfache Möglichkeit, MD5-Prüfsummen einer Dateiliste in Python zu generieren (und zu überprüfen)? (Ich habe ein kleines Programm, an dem ich arbeite, und ich möchte die Prüfsummen der Dateien bestätigen.)

Alexander
quelle
3
Warum nicht einfach benutzen md5sum?
Kennytm
99
Wenn Sie es in Python behalten, können Sie die plattformübergreifende Kompatibilität einfacher verwalten.
Alexander
Wenn Sie eine Lösung mit "Fortschrittsbalken * oder ähnlichem (für sehr große Dateien) wünschen
Laurent LAPORTE
1
@kennytm Der von Ihnen angegebene Link besagt dies im zweiten Absatz: "Der zugrunde liegende MD5-Algorithmus wird bei der Beschreibung nicht mehr als sicher angesehen" md5sum. Deshalb sollten sicherheitsbewusste Programmierer es meiner Meinung nach nicht verwenden.
Debug255
1
@ Debug255 Guter und gültiger Punkt. Beides md5sumund die in dieser SO-Frage beschriebene Technik sollten vermieden werden - es ist besser, wenn möglich SHA-2 oder SHA-3 zu verwenden: en.wikipedia.org/wiki/Secure_Hash_Algorithms
Per Lundberg

Antworten:

462

Sie können hashlib.md5 () verwenden

Beachten Sie, dass Sie manchmal nicht in der Lage sind, die gesamte Datei in den Speicher zu passen. In diesem Fall müssen Sie Blöcke von 4096 Bytes nacheinander lesen und der md5Methode zuführen :

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

Hinweis: hash_md5.hexdigest() Gibt die Hex-Zeichenfolgendarstellung für den Digest zurück, wenn Sie nur die gepackten Bytes verwenden müssen return hash_md5.digest(), damit Sie nicht zurück konvertieren müssen.

quantumSoup
quelle
297

Es gibt einen Weg, der ziemlich ineffizient ist .

einzelne Datei:

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

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

Liste der Dateien:

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

Denken Sie jedoch daran, dass MD5 als fehlerhaft bekannt ist und für keinen Zweck verwendet werden sollte, da die Schwachstellenanalyse sehr schwierig sein kann und eine Analyse der möglichen zukünftigen Verwendung Ihres Codes für Sicherheitsprobleme unmöglich ist. IMHO sollte es direkt aus der Bibliothek entfernt werden, damit jeder, der es verwendet, gezwungen ist, es zu aktualisieren. Folgendes sollten Sie stattdessen tun:

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

Wenn Sie nur Digest im Wert von 128 Bit möchten, können Sie dies tun .digest()[:16].

Dadurch erhalten Sie eine Liste von Tupeln, wobei jedes Tupel den Namen seiner Datei und seinen Hash enthält.

Auch hier stelle ich Ihre Verwendung von MD5 in Frage. Sie sollten mindestens SHA1 verwenden und angesichts der kürzlich in SHA1 entdeckten Fehler wahrscheinlich nicht einmal das. Einige Leute denken, dass es Ihnen gut geht, solange Sie MD5 nicht für "kryptografische" Zwecke verwenden. Aber Dinge haben die Tendenz, einen größeren Umfang zu haben, als Sie ursprünglich erwartet hatten, und Ihre gelegentliche Schwachstellenanalyse kann sich als völlig fehlerhaft erweisen. Es ist am besten, sich einfach daran zu gewöhnen, den richtigen Algorithmus von Anfang an zu verwenden. Es ist nur das Tippen eines anderen Buchstabenbündels. Es ist nicht so schwer.

Hier ist ein Weg, der komplexer, aber speichereffizienter ist :

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]

Und wieder, da MD5 kaputt ist und eigentlich nie mehr verwendet werden sollte:

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

Auch hier können Sie [:16]nach dem Aufruf setzen, hash_bytestr_iter(...)wenn Sie nur 128 Bit Digest wollen.

Allgegenwärtig
quelle
66
Ich verwende nur MD5, um zu bestätigen, dass die Datei nicht beschädigt ist. Ich bin nicht so besorgt darüber, dass es kaputt geht.
Alexander
87
@TheLifelessOne: Und trotz @Omnifarious beängstigender Warnungen ist das eine gute Verwendung von MD5.
Präsident James K. Polk
22
@GregS, @TheLifelessOne - Ja, und als nächstes wissen Sie, dass jemand einen Weg findet, diese Tatsache über Ihre Anwendung zu nutzen, um zu bewirken, dass eine Datei als unbeschädigt akzeptiert wird, wenn es sich überhaupt nicht um die erwartete Datei handelt. Nein, ich stehe zu meinen gruseligen Warnungen. Ich denke, MD5 sollte entfernt werden oder mit Verfallswarnungen kommen.
Omnifarious
10
Ich würde wahrscheinlich .hexdigest () anstelle von .digest () verwenden - es ist für Menschen einfacher zu lesen - was der Zweck von OP ist.
Zbstof
21
Ich habe diese Lösung verwendet, aber sie hat nicht korrekt den gleichen Hash für zwei verschiedene PDF-Dateien angegeben. Die Lösung bestand darin, die Dateien durch Angabe des Binärmodus zu öffnen, dh: [(fname, hashlib.md5 (open (fname, 'rb' ) .read ()). Hexdigest ()) für fname in fnamelst] Dies ist verwandter für die offene Funktion als md5, aber ich dachte, es könnte nützlich sein, sie zu melden, da die oben angegebene plattformübergreifende Kompatibilität erforderlich ist (siehe auch: docs.python.org/2/tutorial/… ).
BlueCoder
34

Ich füge eindeutig nichts grundlegend Neues hinzu, aber ich habe diese Antwort hinzugefügt, bevor ich den Status eines Kommentars erreicht habe, und die Code-Regionen machen die Dinge klarer - jedenfalls speziell, um die Frage von @ Nemo aus der Antwort von Omnifarious zu beantworten:

Ich habe zufällig ein wenig über Prüfsummen nachgedacht (bin hierher gekommen, um nach Vorschlägen für Blockgrößen zu suchen) und habe festgestellt, dass diese Methode möglicherweise schneller ist als erwartet. Nehmen Sie die schnellste (aber ziemlich typische) timeit.timeitoder /usr/bin/timeErgebnis aus jeder von mehreren Methoden der Prüfsumme einer Datei von ca. 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

Es sieht also so aus, als würden sowohl Python als auch / usr / bin / md5sum etwa 30 ms für eine 11-MB-Datei benötigen. Die relevante md5sumFunktion ( md5sum_readin der obigen Auflistung) ist der von Omnifarious ziemlich ähnlich:

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

Zugegeben, diese stammen aus einzelnen Läufen (die mmapsind immer ein bisschen schneller, wenn mindestens ein paar Dutzend Läufe gemacht werden), und meine haben normalerweise ein Extra, f.read(blocksize)nachdem der Puffer erschöpft ist, aber es ist einigermaßen wiederholbar und zeigt, dass dies md5sumin der Befehlszeile der Fall ist nicht unbedingt schneller als eine Python-Implementierung ...

EDIT: Entschuldigung für die lange Verzögerung, ich habe mir das seit einiger Zeit nicht mehr angesehen, aber um die Frage von @ EdRandall zu beantworten, schreibe ich eine Adler32-Implementierung auf. Allerdings habe ich die Benchmarks dafür nicht ausgeführt. Es ist im Grunde das gleiche wie beim CRC32: Anstelle der Init-, Update- und Digest-Aufrufe ist alles ein zlib.adler32()Aufruf:

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

Beachten Sie, dass dies mit der leeren Zeichenfolge beginnen muss, da sich die Adler-Summen tatsächlich unterscheiden, wenn Sie bei Null beginnen, gegenüber ihrer Summe für "", 1dh - CRC kann 0stattdessen beginnen. Das AND-ing wird benötigt, um eine 32-Bit-Ganzzahl ohne Vorzeichen zu erstellen, wodurch sichergestellt wird, dass in allen Python-Versionen derselbe Wert zurückgegeben wird.

rsandwick3
quelle
Könnten Sie möglicherweise ein paar Zeilen hinzufügen, die SHA1 und vielleicht auch zlib.adler32 vergleichen?
Ed Randall
1
Die obige Funktion md5sum () setzt voraus, dass Sie Schreibzugriff auf die Datei haben. Wenn Sie "r + b" im Aufruf von open () durch "rb" ersetzen, funktioniert dies einwandfrei.
Kevin Lyda
1
@EdRandall: adler32 ist es wirklich nicht wert, sich damit zu beschäftigen, z. leviathansecurity.com/blog/analysis-of-adler32
MikeW
6

In Python 3.8+ können Sie dies tun

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

Erwägen Sie hashlib.blake2bstatt md5(nur ersetzen md5mit blake2bin der obigen Snippet). Es ist kryptografisch sicher und schneller als MD5.

Boris
quelle
Der :=Operator ist ein "Zuweisungsoperator" (neu in Python 3.8+). Sie können damit Werte innerhalb eines größeren Ausdrucks zuweisen. Weitere Informationen hier: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Benjamin
0
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()
Johnson
quelle
3
Hallo! Bitte fügen Sie Ihrem Code eine Erklärung hinzu, warum dies eine Lösung für das Problem ist. Darüber hinaus ist dieser Beitrag ziemlich alt, daher sollten Sie auch einige Informationen hinzufügen, warum Ihre Lösung etwas hinzufügt, das die anderen noch nicht angesprochen haben.
d_kennetz
1
Es ist ein weiterer ineffizienter Speicher
Erik Aronesty
-2

Ich denke, dass es etwas bequemer ist, sich auf invoke package und md5sum binary zu verlassen als auf subprocess oder md5 package

import invoke

def get_file_hash(path):

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

Dies setzt natürlich voraus, dass Sie invoke und md5sum installiert haben.

Puchatek
quelle
3
Wenn pathein vom Benutzer angegebener Pfad vorhanden ist, kann jeder Benutzer beliebige Bash-Befehle auf Ihrem System ausführen.
Boris