Hashing di un file in Python


98

Voglio che Python legga in EOF in modo da poter ottenere un hash appropriato, che sia sha1 o md5. Per favore aiuto. Ecco cosa ho finora:

import hashlib

inputFile = raw_input("Enter the name of the file:")
openedFile = open(inputFile)
readFile = openedFile.read()

md5Hash = hashlib.md5(readFile)
md5Hashed = md5Hash.hexdigest()

sha1Hash = hashlib.sha1(readFile)
sha1Hashed = sha1Hash.hexdigest()

print "File Name: %s" % inputFile
print "MD5: %r" % md5Hashed
print "SHA1: %r" % sha1Hashed

6
e qual è il problema?
isedev

1
Voglio che sia in grado di eseguire l'hashing di un file. Ho bisogno che legga fino all'EOF, qualunque sia la dimensione del file.
user3358300

3
questo è esattamente ciò che file.read()fa: leggere l'intero file.
isedev

La documentazione per il read()metodo dice?
Ignacio Vazquez-Abrams,

Dovresti passare attraverso "cos'è l'hashing?".
Sharif Mamun

Risposte:


135

TL; DR utilizza buffer per non utilizzare tonnellate di memoria.

Arriviamo al nocciolo del tuo problema, credo, quando consideriamo le implicazioni sulla memoria del lavoro con file molto grandi . Non vogliamo che questo ragazzaccio sbandieri 2 giga di ram per un file da 2 gigabyte quindi, come fa notare pasztorpisti , dobbiamo occuparci di quei file più grandi in pezzi!

import sys
import hashlib

# BUF_SIZE is totally arbitrary, change for your app!
BUF_SIZE = 65536  # lets read stuff in 64kb chunks!

md5 = hashlib.md5()
sha1 = hashlib.sha1()

with open(sys.argv[1], 'rb') as f:
    while True:
        data = f.read(BUF_SIZE)
        if not data:
            break
        md5.update(data)
        sha1.update(data)

print("MD5: {0}".format(md5.hexdigest()))
print("SHA1: {0}".format(sha1.hexdigest()))

Quello che abbiamo fatto è che stiamo aggiornando i nostri hash di questo ragazzaccio in blocchi da 64kb mentre procediamo con il pratico metodo di aggiornamento dandy di hashlib . In questo modo usiamo molta meno memoria rispetto ai 2 GB necessari per hash il ragazzo tutto in una volta!

Puoi testarlo con:

$ mkfile 2g bigfile
$ python hashes.py bigfile
MD5: a981130cf2b7e09f4686dc273cf7187e
SHA1: 91d50642dd930e9542c39d36f0516d45f4e1af0d
$ md5 bigfile
MD5 (bigfile) = a981130cf2b7e09f4686dc273cf7187e
$ shasum bigfile
91d50642dd930e9542c39d36f0516d45f4e1af0d  bigfile

Spero che aiuti!

Anche tutto questo è delineato nella domanda collegata sul lato destro: Ottieni l'hash MD5 di file di grandi dimensioni in Python


Addendum!

In generale, quando si scrive python è utile prendere l'abitudine di seguire pep-8 . Ad esempio, in Python le variabili sono in genere separate da caratteri di sottolineatura non camelCased. Ma questo è solo stile e nessuno si preoccupa davvero di queste cose tranne le persone che devono leggere uno stile cattivo ... che potresti leggere questo codice tra anni.


@ranman Ciao, non sono riuscito a ottenere la parte {0} ". format (sha1.hexdigest ()). Perché la usiamo invece di usare solo sha1.hexdigest ()?
Belial

@ Belial Cosa non funzionava? Lo stavo usando principalmente per distinguere tra i due hash ...
Randall Hunt,

@ranman Tutto funziona, non l'ho mai usato e non l'ho visto in letteratura. "{0}". Format () ... a me sconosciuto. :)
Belial

1
Come dovrei scegliere BUF_SIZE?
Martin Thoma

1
Questo non genera gli stessi risultati dei shasumbinari. L'altra risposta elencata di seguito (quella che utilizza memoryview) è compatibile con altri strumenti di hashing.
tedivm

61

Per il calcolo corretto ed efficiente del valore hash di un file (in Python 3):

  • Aprire il file in modalità binaria (ovvero aggiungerlo 'b'alla modalità file ) per evitare problemi di codifica dei caratteri e conversione di fine riga.
  • Non leggere il file completo in memoria, poiché è uno spreco di memoria. Invece, leggilo in sequenza blocco per blocco e aggiorna l'hash per ogni blocco.
  • Elimina il doppio buffering, ovvero non utilizzare l'IO bufferizzato, perché utilizziamo già una dimensione di blocco ottimale.
  • Da utilizzare readinto()per evitare il ribaltamento del buffer.

Esempio:

import hashlib

def sha256sum(filename):
    h  = hashlib.sha256()
    b  = bytearray(128*1024)
    mv = memoryview(b)
    with open(filename, 'rb', buffering=0) as f:
        for n in iter(lambda : f.readinto(mv), 0):
            h.update(mv[:n])
    return h.hexdigest()

2
Come fai a sapere qual è la dimensione ottimale del blocco?
Mitar

1
@Mitar, un limite inferiore è il massimo del blocco fisico (tradizionalmente 512 byte o 4KiB con dischi più recenti) e la dimensione della pagina del sistema (4KiB su molti sistemi, altre scelte comuni: 8KiB e 64 KiB). Quindi fondamentalmente fai un po 'di benchmarking e / o guardi i risultati dei benchmark pubblicati e il lavoro correlato (es. Controlla cosa usa rsync / GNU cp / ...).
maxschlepzig

Sarebbe resource.getpagesizedi qualche utilità qui, se volessimo provare a ottimizzarlo in modo un po 'dinamico? E di cosa mmap?
jpmc26

@ jpmc26, getpagesize () non è così utile qui - i valori comuni sono 4 KiB o 8 KiB, qualcosa in quell'intervallo, cioè qualcosa di molto più piccolo di 128 KiB - 128 KiB è generalmente una buona scelta. mmap non aiuta molto nel nostro caso d'uso poiché leggiamo in sequenza il file completo da davanti a dietro. mmap ha dei vantaggi quando il pattern di accesso è più simile ad un accesso casuale, se si accede alle pagine più di una volta e / o se mmap semplifica la gestione del buffer di lettura.
maxschlepzig

3
Ho confrontato sia la soluzione di (1) @Randall Hunt che (2) la tua (in questo ordine, è importante a causa della cache dei file) con un file di circa 116 GB e l'algoritmo sha1sum. La soluzione 1 è stata modificata in modo da utilizzare un buffer di 20 * 4096 (PAGE_SIZE) e impostare il parametro di buffering su 0. È stato modificato solo l'algoritmo della soluzione 2 (sha256 -> sha1). Risultato: (1) 3 m 37,137 s (2) 3 m 30,003 s. Il nativo sha1sum in modalità binaria: 3m31.395s
bioinfornatics

18

Proporrei semplicemente:

def get_digest(file_path):
    h = hashlib.sha256()

    with open(file_path, 'rb') as file:
        while True:
            # Reading is buffered, so we can read smaller chunks.
            chunk = file.read(h.block_size)
            if not chunk:
                break
            h.update(chunk)

    return h.hexdigest()

Tutte le altre risposte qui sembrano complicare troppo. Python sta già eseguendo il buffering durante la lettura (in modo ideale, o lo configuri se hai più informazioni sullo storage sottostante) e quindi è meglio leggere in blocchi la funzione hash trova l'ideale che lo rende più veloce o almeno meno intensivo della CPU per calcolare la funzione hash. Quindi, invece di disabilitare il buffering e provare a emularlo da solo, usi il buffering Python e controlli ciò che dovresti controllare: ciò che il consumatore dei tuoi dati trova ideale, la dimensione del blocco hash.


Risposta perfetta, ma sarebbe carino se sostenessi le tue dichiarazioni con il documento correlato: Python3 - open () e Python2 - open () . Nonostante la differenza tra i due, l'approccio di Python3 è più sofisticato. Tuttavia, ho davvero apprezzato la prospettiva incentrata sul consumatore!
Murmel

hash.block_sizeè documentato proprio come la "dimensione del blocco interno dell'algoritmo hash". Hashlib non lo trova ideale . Niente nella documentazione del pacchetto suggerisce che update()preferisca hash.block_sizeun input dimensionato. Non usa meno CPU se lo chiami così. La tua file.read()chiamata porta a molte creazioni di oggetti non necessarie e copie superflue dal buffer del file al tuo nuovo oggetto chunk bytes.
maxschlepzig

Gli hash aggiornano il loro stato in block_sizeblocchi. Se non li si fornisce in questi blocchi, devono eseguire il buffer e attendere che compaiano dati sufficienti o suddividere internamente i dati in blocchi. Quindi, puoi semplicemente gestirlo all'esterno e quindi semplificare ciò che accade internamente. Trovo questo ideale. Vedi ad esempio: stackoverflow.com/a/51335622/252025
Mitar

Il block_sizeè molto più piccolo di qualsiasi dimensione di lettura utile. Inoltre, qualsiasi blocco utile e dimensione di lettura sono potenze di due. Pertanto, la dimensione di lettura è divisibile per la dimensione del blocco per tutte le letture tranne forse l'ultima. Ad esempio, la dimensione del blocco sha256 è 64 byte. Ciò significa che update()è in grado di elaborare direttamente l'input senza alcun buffering fino a un multiplo di block_size. Pertanto, solo se l'ultima lettura non è divisibile per la dimensione del blocco, deve bufferizzare fino a 63 byte, una volta. Quindi, il tuo ultimo commento non è corretto e non supporta le affermazioni che stai facendo nella tua risposta.
maxschlepzig

Il punto è che non è necessario ottimizzare il buffering perché è già fatto da Python durante la lettura. Quindi devi solo decidere la quantità di loop che vuoi fare quando si esegue l'hashing su quel buffer esistente.
Mitar

5

Ho programmato un modulo che è in grado di eseguire l'hashing di file di grandi dimensioni con diversi algoritmi.

pip3 install py_essentials

Usa il modulo in questo modo:

from py_essentials import hashing as hs
hash = hs.fileChecksum("path/to/the/file.txt", "sha256")

3

Ecco una soluzione Python 3, POSIX (non Windows!) Che utilizza mmapper mappare l'oggetto in memoria.

import hashlib
import mmap

def sha256sum(filename):
    h  = hashlib.sha256()
    with open(filename, 'rb') as f:
        with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mm:
            h.update(mm)
    return h.hexdigest()

-2
import hashlib
user = input("Enter ")
h = hashlib.md5(user.encode())
h2 = h.hexdigest()
with open("encrypted.txt","w") as e:
    print(h2,file=e)


with open("encrypted.txt","r") as e:
    p = e.readline().strip()
    print(p)

2
Fondamentalmente stai facendo echo $USER_INPUT | md5sum > encrypted.txt && cat encrypted.txtche non si occupa dell'hashing dei file, specialmente non di quelli grandi.
Murmel

hashing! = encrypting
bugmenot123
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.