Haszowanie pliku w Pythonie

101

Chcę, aby Python czytał do EOF, aby uzyskać odpowiedni skrót, niezależnie od tego, czy jest to sha1 czy md5. Proszę pomóż. Oto, co mam do tej pory:

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
user3358300
źródło
6
a jaki jest problem?
isedev
1
Chcę, aby mógł haszować plik. Potrzebuję go do przeczytania do EOF, niezależnie od rozmiaru pliku.
user3358300
3
to jest dokładnie to, co file.read()robi - przeczytaj cały plik.
isedev
Dokumentacja dotycząca read()metody mówi?
Ignacio Vazquez-Abrams
Powinieneś przejść przez „co to jest haszowanie?”.
Sharif Mamun

Odpowiedzi:

141

TL; DR używają buforów, aby nie używać ton pamięci.

Myślę, że dochodzimy do sedna twojego problemu, gdy rozważymy implikacje pamięciowe pracy z bardzo dużymi plikami . Nie chcemy, aby ten zły chłopiec przerzucił 2 gigabajty pamięci RAM dla pliku o wielkości 2 gigabajtów, więc jak wskazuje pasztorpisti , musimy poradzić sobie z większymi plikami w kawałkach!

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

To, co zrobiliśmy, to aktualizowanie naszych skrótów tego złego chłopca w fragmentach 64kb, wraz z poręczną metodą aktualizacji hashlib . W ten sposób zużywamy o wiele mniej pamięci niż 2 GB potrzebne do zhaszowania faceta na raz!

Możesz to sprawdzić za pomocą:

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

Mam nadzieję, że to pomoże!

Wszystko to jest również opisane w powiązanym pytaniu po prawej stronie: Pobierz skrót MD5 dużych plików w Pythonie


Uzupełnienie!

Generalnie, pisząc w Pythonie, warto nabrać nawyku podążania za pep-8 . Na przykład w pythonie zmienne są zwykle oddzielane podkreśleniem, a nie camelCased. Ale to tylko styl i nikogo tak naprawdę nie obchodzą te rzeczy, z wyjątkiem ludzi, którzy muszą czytać w złym stylu ... może to być Ty czytasz ten kod za lata.

Randall Hunt
źródło
@ranman Witaj, nie mogę pobrać części {0}. format (sha1.hexdigest ()). Dlaczego używamy jej zamiast po prostu używać sha1.hexdigest ()?
Belial
@Belial Co nie działało? Używałem tego głównie do rozróżnienia dwóch skrótów ...
Randall Hunt,
@ranman Wszystko działa, po prostu nigdy tego nie używałem i nie widziałem tego w literaturze. „{0}”. Format () ... nieznany mi. :)
Belial
1
Jak mam wybrać BUF_SIZE?
Martin Thoma
1
Nie generuje to takich samych wyników jak shasumpliki binarne. Druga odpowiedź wymieniona poniżej (ta wykorzystująca widok pamięci) jest kompatybilna z innymi narzędziami do mieszania.
tedivm
61

W celu prawidłowego i wydajnego obliczenia wartości skrótu pliku (w Pythonie 3):

  • Otwórz plik w trybie binarnym (tj. Dodaj 'b'do trybu pliku), aby uniknąć problemów z kodowaniem znaków i konwersją końca linii.
  • Nie czytaj całego pliku do pamięci, ponieważ jest to marnowanie pamięci. Zamiast tego odczytuj go sekwencyjnie blok po bloku i aktualizuj skrót dla każdego bloku.
  • Wyeliminuj podwójne buforowanie, tj. Nie używaj buforowanego IO, ponieważ już używamy optymalnego rozmiaru bloku.
  • Służy readinto()do zapobiegania ubijaniu buforów.

Przykład:

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()
maxschlepzig
źródło
2
Skąd wiesz, jaki jest optymalny rozmiar bloku?
Mitar
1
@Mitar, dolna granica to maksimum fizycznego bloku (tradycyjnie 512 bajtów lub 4 KB z nowszymi dyskami) i rozmiar strony systemu (4 KB w wielu systemach, inne popularne opcje: 8 KB i 64 KB). Następnie w zasadzie przeprowadzasz testy porównawcze i / lub przeglądasz opublikowane wyniki testów porównawczych i związaną z nimi pracę (np. Sprawdzasz, czego aktualnie używa rsync / GNU cp / ...).
maxschlepzig
Przydałoby resource.getpagesizesię tutaj, gdybyśmy chcieli spróbować ją nieco dynamicznie zoptymalizować? A co z tym mmap?
jpmc26
@ jpmc26, getpagesize () nie jest tutaj zbyt przydatne - typowe wartości to 4 KiB lub 8 KiB, coś w tym zakresie, tj. coś znacznie mniejszego niż 128 KiB - 128 KiB jest ogólnie dobrym wyborem. mmap niewiele pomaga w naszym przypadku użycia, ponieważ sekwencyjnie czytamy cały plik od początku do końca. mmap ma zalety, gdy wzorzec dostępu jest bardziej przypadkowy, jak strony są otwierane więcej niż raz i / lub jeśli tak, mmap upraszcza zarządzanie buforem odczytu.
maxschlepzig
3
Porównałem zarówno rozwiązanie (1) @Randall Hunt, jak i (2) twoje (w tej kolejności jest to ważne ze względu na pamięć podręczną plików) z plikiem o wielkości około 116 GB i algorytmem sha1sum. Rozwiązanie 1 zostało zmodyfikowane w celu użycia bufora 20 * 4096 (PAGE_SIZE) i ustawiono parametr buforowania na 0. Zmodyfikowano algorytm tylko dla rozwiązania 2 (sha256 -> sha1). Wynik: (1) 3m37,137s (2) 3m30,003s. Natywna suma sha1 w trybie binarnym: 3m31,395s
bioinfornatics
18

Proponuję po prostu:

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

Wszystkie inne odpowiedzi wydają się zbyt skomplikowane. Python już buforuje podczas odczytu (w idealny sposób lub konfigurujesz buforowanie, jeśli masz więcej informacji o podstawowej pamięci), więc lepiej jest czytać fragmentami, które funkcja skrótu uzna za idealną, co przyspiesza lub co najmniej zmniejsza obciążenie procesora oblicz funkcję skrótu. Więc zamiast wyłączać buforowanie i próbować emulować je samemu, używasz buforowania Pythona i kontrolujesz to, co powinieneś kontrolować: to, co konsument Twoich danych uważa za idealny, rozmiar bloku mieszania.

Mitar
źródło
Doskonała odpowiedź, ale byłoby miło, gdybyś poparł swoje instrukcje odpowiednim dokumentem: Python3 - open () i Python2 - open () . Nawet biorąc pod uwagę różnicę między nimi, podejście Python3 jest bardziej wyrafinowane. Niemniej jednak naprawdę doceniam perspektywę zorientowaną na konsumenta!
Murmel
hash.block_sizejest udokumentowany tak samo, jak „wewnętrzny rozmiar bloku algorytmu wyznaczania wartości skrótu”. Hashlib nie uważa tego za idealne . Nic w dokumentacji pakietu nie sugeruje, że update()preferuje hash.block_sizedane wejściowe o rozmiarze. Nie zużywa mniej procesora, jeśli tak to nazywasz. Twoje file.read()wywołanie prowadzi do wielu niepotrzebnych kreacji obiektów i zbędnych kopii z bufora pliku do nowego obiektu porcji bajtów.
maxschlepzig
Hashe aktualizują swój stan block_sizefragmentami. Jeśli nie dostarczasz ich w tych fragmentach, muszą buforować i czekać na pojawienie się wystarczającej ilości danych lub wewnętrznie podzielić dane na porcje. Możesz więc poradzić sobie z tym na zewnątrz, a następnie uprościć to, co dzieje się wewnętrznie. Uważam, że to ideał. Zobacz na przykład: stackoverflow.com/a/51335622/252025
Mitar
block_sizeJest znacznie mniejsza niż wszelkich użytecznych wielkości odczytu. Ponadto wszelkie przydatne rozmiary bloków i odczytów są potęgami dwójki. Tak więc rozmiar odczytu jest podzielny przez rozmiar bloku dla wszystkich odczytów z wyjątkiem być może ostatniego. Na przykład rozmiar bloku sha256 wynosi 64 bajty. Oznacza to, że update()jest w stanie bezpośrednio przetwarzać dane wejściowe bez buforowania do wielokrotności block_size. Zatem tylko wtedy, gdy ostatni odczyt nie jest podzielny przez rozmiar bloku, musi jednorazowo buforować do 63 bajtów. Dlatego też Twój ostatni komentarz jest niepoprawny i nie potwierdza twierdzeń, które zawarłeś w swojej odpowiedzi.
maxschlepzig
Chodzi o to, że nie trzeba optymalizować buforowania, ponieważ jest to już wykonywane przez Pythona podczas czytania. Musisz więc tylko zdecydować, ile pętli chcesz wykonać podczas mieszania w istniejącym buforze.
Mitar
6

Zaprogramowałem moduł, który jest w stanie haszować duże pliki różnymi algorytmami.

pip3 install py_essentials

Użyj modułu w następujący sposób:

from py_essentials import hashing as hs
hash = hs.fileChecksum("path/to/the/file.txt", "sha256")
fyyl
źródło
Czy jest to platforma wieloplatformowa (Linux + Win)? Czy to działa z Python3? Czy nadal jest utrzymywany?
Basj
5

Oto Python 3, rozwiązanie POSIX (nie Windows!), Które używa mmapdo mapowania obiektu do pamięci.

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()
Antti Haapala
źródło
Naiwne pytanie ... jakie są zalety używania mmapw tym scenariuszu?
Jonathan B.
1
@JonathanB. większość metod niepotrzebnie tworzy bytesobiekty w pamięci i wywołuje readzbyt wiele lub zbyt mało razy. Spowoduje to zmapowanie pliku bezpośrednio do pamięci wirtualnej i zhaszowanie go stamtąd - system operacyjny może mapować zawartość pliku bezpośrednio z pamięci podręcznej bufora do procesu odczytu. Oznacza to, że może to być znacznie szybsze niż ten
Antti Haapala
@JonathanB. Zrobiłem test i różnica nie jest tak znacząca w tym przypadku, mówimy o ~ 15% w porównaniu z metodą naiwną.
Antti Haapala
-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)
Ome Mishra
źródło
2
Zasadniczo robisz to, echo $USER_INPUT | md5sum > encrypted.txt && cat encrypted.txtco nie zajmuje się haszowaniem plików, szczególnie nie z dużymi.
Murmel
1
hashing! = szyfrowanie
bugmenot123