Generazione di un checksum MD5 di un file


348

Esiste un modo semplice per generare (e verificare) checksum MD5 di un elenco di file in Python? (Ho un piccolo programma su cui sto lavorando e vorrei confermare i checksum dei file).


3
Perché non usare solo md5sum?
kennytm,

99
Mantenerlo in Python semplifica la gestione della compatibilità multipiattaforma.
Alexander

Se si vuole de soluzione "barra di avanzamento * o simili (per file molto grandi), prendere in considerazione questa soluzione: stackoverflow.com/questions/1131220/...
Laurent LAPORTE

1
@kennytm Il link che hai fornito dice questo nel secondo paragrafo: "L'algoritmo MD5 sottostante non è più considerato sicuro" durante la descrizione md5sum. Ecco perché i programmatori attenti alla sicurezza non dovrebbero usarlo secondo me.
Debug255,

1
@ Debug255 Punto valido e valido. Entrambi md5sume la tecnica descritta in questa domanda SO dovrebbero essere evitati - è meglio usare SHA-2 o SHA-3, se possibile: en.wikipedia.org/wiki/Secure_Hash_Algorithms
Per Lundberg

Risposte:


462

Puoi usare hashlib.md5 ()

Si noti che a volte non sarà possibile adattare l'intero file in memoria. In tal caso, dovrai leggere blocchi di 4096 byte in sequenza e inserirli nel md5metodo:

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

Nota: hash_md5.hexdigest() restituirà la rappresentazione della stringa esadecimale per il digest, se hai solo bisogno di usare i byte compressi return hash_md5.digest(), quindi non devi riconvertire.


297

C'è un modo in cui la memoria è piuttosto inefficiente .

file singolo:

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

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

elenco di file:

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

Ricordiamo, tuttavia, che MD5 è noto non funzionante e non dovrebbe essere utilizzato per nessuno scopo poiché l'analisi della vulnerabilità può essere davvero complicata e analizzare qualsiasi possibile utilizzo futuro a cui il codice potrebbe essere sottoposto per problemi di sicurezza è impossibile. IMHO, dovrebbe essere rimosso dalla libreria in modo che tutti coloro che lo usano siano costretti ad aggiornare. Quindi, ecco cosa dovresti fare invece:

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

Se vuoi solo 128 bit di valore digest puoi farlo .digest()[:16].

Questo ti darà un elenco di tuple, ogni tupla contenente il nome del suo file e il suo hash.

Ancora una volta metto fortemente in dubbio il tuo utilizzo di MD5. Dovresti almeno usare SHA1, e dati i recenti difetti scoperti in SHA1 , probabilmente nemmeno quello. Alcune persone pensano che finché non usi MD5 per scopi "crittografici", stai bene. Ma le cose hanno la tendenza a finire per essere più ampie di quanto ti aspetti inizialmente, e l'analisi della tua vulnerabilità casuale potrebbe rivelarsi completamente imperfetta. È meglio prendere l'abitudine di usare l'algoritmo giusto fuori dal gate. Basta digitare un diverso gruppo di lettere è tutto. Non è così difficile.

Ecco un modo più complesso, ma efficiente in termini di memoria :

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]

E, ancora una volta, poiché MD5 è rotto e non dovrebbe più essere utilizzato:

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

Ancora una volta, puoi mettere [:16]dopo la chiamata hash_bytestr_iter(...)se vuoi solo 128 bit di valore digest.


66
Sto solo usando MD5 per confermare che il file non è danneggiato. Non sono così preoccupato per la sua rottura.
Alexander,

87
@TheLifelessOne: E nonostante @Omnifarious avvisi spaventosi, questo è un ottimo uso di MD5.
Presidente James K. Polk,

22
@GregS, @TheLifelessOne - Sì, e la prossima cosa che conosci qualcuno trova un modo per usare questo fatto sulla tua applicazione per fare in modo che un file venga accettato come non corrotto quando non è il file che ti aspetti affatto. No, seguo i miei avvertimenti spaventosi. Penso che MD5 dovrebbe essere rimosso o fornito con avvisi di deprecazione.
Onnipotente il

10
Probabilmente userei .hexdigest () invece di .digest () - è più facile da leggere per gli umani - che è lo scopo di OP.
zbstof,

21
Ho usato questa soluzione ma ha dato erroneamente lo stesso hash per due diversi file pdf. La soluzione era aprire i file specificando la modalità binaria, ovvero: [(fname, hashlib.md5 (open (fname, 'rb' ) .read ()). Hexdigest ()) per fname in fnamelst] Questo è più correlato alla funzione aperta di md5 ma ho pensato che potesse essere utile segnalarlo dato il requisito di compatibilità multipiattaforma sopra indicato (vedi anche: docs.python.org/2/tutorial/… ).
BlueCoder,

34

Chiaramente non sto aggiungendo nulla di fondamentalmente nuovo, ma ho aggiunto questa risposta prima di passare allo stato di commento, inoltre le aree di codice rendono le cose più chiare - comunque, in particolare per rispondere alla domanda di @ Nemo dalla risposta di Omnifarious:

Mi è capitato di pensare un po 'ai checksum (sono venuto qui alla ricerca di suggerimenti sulle dimensioni dei blocchi, in particolare) e ho scoperto che questo metodo potrebbe essere più veloce di quanto ti aspetteresti. Prendendo il più veloce (ma piuttosto tipico) timeit.timeito il /usr/bin/timerisultato di ciascuno dei diversi metodi di controllo di un file di ca. 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

Quindi, sembra che sia Python che / usr / bin / md5sum impieghino circa 30ms per un file da 11 MB. La md5sumfunzione pertinente ( md5sum_readnella lista sopra) è abbastanza simile a quella di 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()

Certo, questi provengono da singole corse ( mmapquelle sono sempre più veloci quando vengono fatte almeno alcune dozzine di corse), e il mio di solito ha un extra f.read(blocksize)dopo che il buffer è esaurito, ma è ragionevolmente ripetibile e mostra che md5sumsulla riga di comando è non necessariamente più veloce di un'implementazione di Python ...

EDIT: Mi dispiace per il lungo ritardo, non ci guardo da un po 'di tempo, ma per rispondere alla domanda di @ EdRandall, scriverò un'implementazione di Adler32. Tuttavia, non ho eseguito i benchmark per questo. È praticamente lo stesso di CRC32: al posto delle chiamate init, update e digest, tutto è una zlib.adler32()chiamata:

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

Si noti che questo deve iniziare con la stringa vuota, poiché le somme di Adler differiscono effettivamente quando si parte da zero rispetto alla loro somma per "", ovvero 1: CRC può iniziare con 0. IlAND -ing è necessario per renderlo un numero intero senza segno a 32 bit, il che garantisce che restituisca lo stesso valore tra le versioni di Python.


Potresti forse aggiungere un paio di righe confrontando SHA1 e forse anche zlib.adler32?
Ed Randall,

1
La funzione md5sum () sopra presuppone che tu abbia accesso in scrittura al file. Se sostituisci "r + b" nella chiamata open () con "rb" funzionerà bene.
Kevin Lyda,

1
@EdRandall: adler32 non vale davvero la pena preoccuparsi, ad es. leviathansecurity.com/blog/analysis-of-adler32
MikeW

6

In Python 3.8+ puoi farlo

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

Prendi in considerazione l'utilizzo hashlib.blake2binvece di md5(sostituisci semplicemente md5con blake2bnello snippet sopra). È crittograficamente sicuro e più veloce di MD5.


L' :=operatore è un "operatore di assegnazione" (nuovo in Python 3.8+); ti permette di assegnare valori all'interno di un'espressione più grande; maggiori informazioni qui: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Benjamin

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

3
Ciao! Aggiungi una spiegazione al codice sul perché questa è una soluzione al problema. Inoltre, questo post è piuttosto vecchio, quindi dovresti anche aggiungere alcune informazioni sul perché la tua soluzione aggiunge qualcosa che gli altri non hanno già affrontato.
d_kennetz,

1
È un altro modo inefficace della memoria
Erik Aronesty il

-2

Penso che fare affidamento sul pacchetto invoke e sul binario md5sum sia un po 'più conveniente del sottoprocesso o del pacchetto md5

import invoke

def get_file_hash(path):

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

Questo ovviamente presuppone che tu abbia invocato e md5sum installato.


3
Se pathè un percorso fornito dall'utente, ciò consentirà a qualsiasi utente di eseguire comandi bash arbitrari sul proprio sistema.
Boris,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.