Holen Sie sich den MD5-Hash großer Dateien in Python

Lesezeit: 9 Minuten

Benutzeravatar von JustRegisterMe
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?”

    – XTL

    24. Februar 2012 um 12:29 Uhr

  • ab 3.11 hashlib gewann die file_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

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 open stets ö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

Benutzeravatar von Yuval Adam
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

Benutzeravatar von Piotr Czapla
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

Benutzeravatar von Nathan Feger
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()

Benutzeravatar von Bastien Semene
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()
  • Das ist Pythonisch
  • Dies ist eine Funktion
  • 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.

    – Bastien Samen

    27. August 2013 um 8:17 Uhr


  • Können Sie erläutern, wie „hr“ hier funktioniert?

    – EnemyBagJones

    23. März 2018 um 18:19 Uhr

  • @EnemyBagJones ‘hr’ steht für menschenlesbar. Es gibt eine Zeichenfolge mit 32 Zeichen langen Hexadezimalziffern zurück: docs.python.org/2/library/md5.html#md5.md5.hexdigest

    – Bastien Samen

    27. März 2018 um 9:46 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

Eine tragbare Python 2/3-Lösung

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.

    – Bastien Samen

    27. August 2013 um 8:17 Uhr


  • Können Sie erläutern, wie „hr“ hier funktioniert?

    – EnemyBagJones

    23. März 2018 um 18:19 Uhr

  • @EnemyBagJones ‘hr’ steht für menschenlesbar. Es gibt eine Zeichenfolge mit 32 Zeichen langen Hexadezimalziffern zurück: docs.python.org/2/library/md5.html#md5.md5.hexdigest

    – Bastien Samen

    27. März 2018 um 9:46 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

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

1435950cookie-checkHolen Sie sich den MD5-Hash großer Dateien in Python

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy