Holen Sie sich den MD5-Hash großer Dateien in Python
Lesezeit: 9 Minuten
Registriere mich einfach
Ich habe benutzt Hashlib (was ersetzt md5 in Python 2.6/3.0), und es funktionierte gut, wenn ich eine Datei öffnete und ihren Inhalt in die hashlib.md5() Funktion.
Das Problem bei sehr großen Dateien besteht darin, dass ihre Größe die RAM-Größe überschreiten kann.
Wie kann ich den MD5-Hash einer Datei abrufen, ohne die gesamte Datei in den Speicher zu laden?
Ich würde umformulieren: “Wie bekommt man den MD5 von einer Datei, ohne die ganze Datei in den Speicher zu laden?”
Sie müssen die Datei in Abschnitten geeigneter Größe lesen:
def md5_for_file(f, block_size=2**20):
md5 = hashlib.md5()
while True:
data = f.read(block_size)
if not data:
break
md5.update(data)
return md5.digest()
Hinweis: Stellen Sie sicher, dass Sie Ihre Datei mit dem ‘rb’ zum Öffnen öffnen – sonst erhalten Sie ein falsches Ergebnis.
Um also alles in einer Methode zu erledigen, verwenden Sie so etwas wie:
def generate_file_md5(rootdir, filename, blocksize=2**20):
m = hashlib.md5()
with open( os.path.join(rootdir, filename) , "rb" ) as f:
while True:
buf = f.read(blocksize)
if not buf:
break
m.update( buf )
return m.hexdigest()
Das obige Update basierte auf den Kommentaren von Frerich Raabe – und ich habe dies getestet und festgestellt, dass es auf meiner Windows-Installation von Python 2.7.2 korrekt ist
Ich habe die Ergebnisse mit dem überprüft Jacksum Werkzeug.
jacksum -a md5 <filename>
Wichtig zu beachten ist, dass die Datei, die an diese Funktion übergeben wird, im Binärmodus geöffnet werden muss, dh durch Übergabe rb zum open Funktion.
– Frerich Raabe
21. Juli 2011 um 13:02 Uhr
Dies ist eine einfache Ergänzung, aber mit hexdigest Anstatt von digest erzeugt einen hexadezimalen Hash, der wie die meisten Hash-Beispiele “aussieht”.
– Tschaymore
16. Oktober 2011 um 2:26 Uhr
Sollte es nicht sein if len(data) < block_size: break?
– Erik Kaplun
2. November 2012 um 10:35 Uhr
Erik, nein, warum sollte es sein? Das Ziel ist, MD5 alle Bytes bis zum Ende der Datei zuzuführen. Das Erhalten eines Teilblocks bedeutet nicht, dass nicht alle Bytes der Prüfsumme zugeführt werden sollten.
– Benutzer25148
2. November 2012 um 20:12 Uhr
@ user2084795 openstets öffnet ein neues Datei-Handle, dessen Position auf den Anfang der Datei gesetzt ist, (es sei denn, Sie öffnen eine Datei zum Anhängen).
– Steve Barnes
5. Juli 2017 um 9:15 Uhr
Yuval Adam
Brechen Sie die Datei in 8192-Byte-Blöcke (oder ein anderes Vielfaches von 128 Bytes) auf und füttern Sie sie nacheinander mit MD5 update().
Dies nutzt die Tatsache aus, dass MD5 128-Byte-Digest-Blöcke hat (8192 ist 128 × 64). Da Sie nicht die gesamte Datei in den Speicher einlesen, verbraucht dies nicht viel mehr als 8192 Byte Speicher.
In Python 3.8+ können Sie das 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
Sie können genauso effektiv eine Blockgröße von einem beliebigen Vielfachen von 128 verwenden (z. B. 8192, 32768 usw.), und das ist viel schneller als das gleichzeitige Lesen von 128 Bytes.
– jmanning2k
15. Juli 2009 um 15:09 Uhr
Danke jmanning2k für diesen wichtigen Hinweis, ein Test auf 184 MB Datei dauert (0m9.230s, 0m2.547s, 0m2.429s) mit (128, 8192, 32768), ich werde 8192 verwenden, da der höhere Wert einen nicht wahrnehmbaren Effekt hat.
– Registriere mich einfach
17. Juli 2009 um 19:33 Uhr
Wenn Sie können, sollten Sie verwenden hashlib.blake2b Anstatt von md5. Im Gegensatz zu MD5, BLAKE2 ist sicher und noch schneller.
– Boris Werchowskij
22. November 2019 um 11:59 Uhr
@Boris, man kann eigentlich nicht sagen, dass BLAKE2 sicher ist. Man kann nur sagen, dass es noch nicht kaputt gegangen ist.
– vy32
8. April 2020 um 14:57 Uhr
@vy32 Sie können auch nicht sagen, dass es definitiv kaputt gehen wird. Wir werden in 100 Jahren sehen, aber es ist zumindest besser als MD5, das definitiv unsicher ist.
– Boris Werchowskij
8. April 2020 um 15:50 Uhr
Piotr Czapla
Python < 3.7
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
h.update(chunk)
return h.digest()
Python 3.8 und höher
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
while chunk := f.read(chunk_num_blocks*h.block_size):
h.update(chunk)
return h.digest()
Ursprünglicher Beitrag
Wenn Sie eine pythonischere (Nr while True) zum Lesen der Datei, überprüfen Sie diesen Code:
import hashlib
def checksum_md5(filename):
md5 = hashlib.md5()
with open(filename,'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
md5.update(chunk)
return md5.digest()
Notiere dass der iter() Die Funktion benötigt eine leere Bytezeichenfolge, damit der zurückgegebene Iterator bei EOF anhält, da read() kehrt zurück b'' (nicht nur '').
Besser noch, verwenden Sie so etwas wie 128*md5.block_size Anstatt von 8192.
– mrkj
6. Januar 2011 um 22:51 Uhr
mrkj: Ich denke, es ist wichtiger, die Leseblockgröße basierend auf Ihrer Festplatte auszuwählen und dann sicherzustellen, dass es ein Vielfaches davon ist md5.block_size.
– Harvey
12. April 2013 um 14:10 Uhr
das b'' Syntax war mir neu. Hier erklärt.
– cod3monk3y
18. Februar 2014 um 5:19 Uhr
@ThorSummoner: Nicht wirklich, aber von meiner Suche nach optimalen Blockgrößen für Flash-Speicher würde ich vorschlagen, einfach eine Zahl wie 32k oder etwas, das sich leicht durch 4, 8 oder 16k teilen lässt, auszuwählen. Wenn Ihre Blockgröße beispielsweise 8 KB beträgt, entspricht das Lesen von 32 KB 4 Lesevorgängen bei der richtigen Blockgröße. Wenn es 16 ist, dann 2. Aber in jedem Fall sind wir gut, weil wir zufällig ein ganzzahliges Vielfaches von Blöcken lesen.
– Harvey
16. März 2015 um 14:21 Uhr
“while True” ist ziemlich pythonisch.
– Jürgen A. Erhard
16. Dezember 2015 um 9:07 Uhr
Nathan Feger
Hier ist meine Version der Methode von Piotr Czapla:
def md5sum(filename):
md5 = hashlib.md5()
with open(filename, 'rb') as f:
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
md5.update(chunk)
return md5.hexdigest()
Bastien Samen
Unter Verwendung mehrerer Kommentare/Antworten für diese Frage ist hier meine Lösung:
import hashlib
def md5_for_file(path, block_size=256*128, hr=False):
'''
Block size directly depends on the block size of your filesystem
to avoid performances issues
Here I have blocks of 4096 octets (Default NTFS)
'''
md5 = hashlib.md5()
with open(path,'rb') as f:
for chunk in iter(lambda: f.read(block_size), b''):
md5.update(chunk)
if hr:
return md5.hexdigest()
return md5.digest()
Es vermeidet implizite Werte: immer explizite bevorzugen.
Es ermöglicht (sehr wichtige) Leistungsoptimierungen
Ein Vorschlag: Machen Sie Ihr md5-Objekt zu einem optionalen Parameter der Funktion, damit alternative Hash-Funktionen wie sha256 MD5 problemlos ersetzen können. Ich werde dies auch als Bearbeitung vorschlagen.
– Falkenflügel
15. August 2013 um 19:41 Uhr
außerdem: Digest ist nicht menschenlesbar. hexdigest() ermöglicht eine verständlichere, allgemein erkennbare Ausgabe sowie einen einfacheren Austausch des Hashs
– Falkenflügel
15. August 2013 um 19:51 Uhr
Andere Hash-Formate liegen außerhalb des Geltungsbereichs der Frage, aber der Vorschlag ist für eine allgemeinere Funktion relevant. Ich habe gemäß Ihrem zweiten Vorschlag eine “vom Menschen lesbare” Option hinzugefügt.
Um eine Prüfsumme (md5, sha1 usw.) zu berechnen, müssen Sie die Datei im Binärmodus öffnen, da Sie Bytewerte summieren:
Um Python 2.7 und Python 3 portabel zu sein, sollten Sie die verwenden io Pakete, etwa so:
import hashlib
import io
def md5sum(src):
md5 = hashlib.md5()
with io.open(src, mode="rb") as fd:
content = fd.read()
md5.update(content)
return md5
Wenn Ihre Dateien groß sind, ziehen Sie es möglicherweise vor, die Datei stückweise zu lesen, um zu vermeiden, dass der gesamte Dateiinhalt im Speicher gespeichert wird:
def md5sum(src, length=io.DEFAULT_BUFFER_SIZE):
md5 = hashlib.md5()
with io.open(src, mode="rb") as fd:
for chunk in iter(lambda: fd.read(length), b''):
md5.update(chunk)
return md5
Der Trick dabei ist, die zu verwenden iter() Funktion mit a Wächter (die leere Zeichenfolge).
Der in diesem Fall erstellte Iterator ruft auf Ö [the lambda function] ohne Argumente für jeden Aufruf von its next() Methode; wenn der zurückgegebene Wert gleich Sentinel ist, StopIteration wird erhöht, andernfalls wird der Wert zurückgegeben.
Wenn Ihre Dateien sind Ja wirklich groß, müssen Sie möglicherweise auch Fortschrittsinformationen anzeigen. Sie können dies tun, indem Sie eine Callback-Funktion aufrufen, die die Menge der berechneten Bytes druckt oder protokolliert:
def md5sum(src, callback, length=io.DEFAULT_BUFFER_SIZE):
calculated = 0
md5 = hashlib.md5()
with io.open(src, mode="rb") as fd:
for chunk in iter(lambda: fd.read(length), b''):
md5.update(chunk)
calculated += len(chunk)
callback(calculated)
return md5
Ein Vorschlag: Machen Sie Ihr md5-Objekt zu einem optionalen Parameter der Funktion, damit alternative Hash-Funktionen wie sha256 MD5 problemlos ersetzen können. Ich werde dies auch als Bearbeitung vorschlagen.
– Falkenflügel
15. August 2013 um 19:41 Uhr
außerdem: Digest ist nicht menschenlesbar. hexdigest() ermöglicht eine verständlichere, allgemein erkennbare Ausgabe sowie einen einfacheren Austausch des Hashs
– Falkenflügel
15. August 2013 um 19:51 Uhr
Andere Hash-Formate liegen außerhalb des Geltungsbereichs der Frage, aber der Vorschlag ist für eine allgemeinere Funktion relevant. Ich habe gemäß Ihrem zweiten Vorschlag eine “vom Menschen lesbare” Option hinzugefügt.
Ein Remix von Bastien Semenes Code, der den Hawkwing-Kommentar zur generischen Hash-Funktion berücksichtigt …
def hash_for_file(path, algorithm=hashlib.algorithms[0], block_size=256*128, human_readable=True):
"""
Block size directly depends on the block size of your filesystem
to avoid performances issues
Here I have blocks of 4096 octets (Default NTFS)
Linux Ext4 block size
sudo tune2fs -l /dev/sda5 | grep -i 'block size'
> Block size: 4096
Input:
path: a path
algorithm: an algorithm in hashlib.algorithms
ATM: ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
block_size: a multiple of 128 corresponding to the block size of your filesystem
human_readable: switch between digest() or hexdigest() output, default hexdigest()
Output:
hash
"""
if algorithm not in hashlib.algorithms:
raise NameError('The algorithm "{algorithm}" you specified is '
'not a member of "hashlib.algorithms"'.format(algorithm=algorithm))
hash_algo = hashlib.new(algorithm) # According to hashlib documentation using new()
# will be slower then calling using named
# constructors, ex.: hashlib.md5()
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(block_size), b''):
hash_algo.update(chunk)
if human_readable:
file_hash = hash_algo.hexdigest()
else:
file_hash = hash_algo.digest()
return file_hash
14359500cookie-checkHolen Sie sich den MD5-Hash großer Dateien in Pythonyes
Ich würde umformulieren: “Wie bekommt man den MD5 von einer Datei, ohne die ganze Datei in den Speicher zu laden?”
– XTL
24. Februar 2012 um 12:29 Uhr
ab 3.11
hashlib
gewann diefile_digest
Funktion, die Ihnen die Mühe zu nehmen scheint, umfangreiche Boilerplates zu schreiben docs.python.org/3.11/library/hashlib.html#hashlib.file_digest– pseifert
8. November um 14:10 Uhr