Hash de un archivo en Python

101

Quiero que Python lea en el EOF para poder obtener un hash apropiado, ya sea sha1 o md5. Por favor ayuda. Esto es lo que tengo hasta ahora:

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
usuario3358300
fuente
6
¿Y cuál es el problema?
isedev
1
Quiero que pueda hacer un hash en un archivo. Lo necesito para leer hasta el EOF, cualquiera que sea el tamaño del archivo.
user3358300
3
eso es exactamente lo que file.read()hace: leer todo el archivo.
isedev
¿La documentación del read()método dice?
Ignacio Vazquez-Abrams
Debería pasar por "¿qué es el hash?".
Sharif Mamun

Respuestas:

141

TL; DR usa búferes para no usar toneladas de memoria.

Llegamos al meollo de su problema, creo, cuando consideramos las implicaciones de memoria de trabajar con archivos muy grandes . No queremos que este chico malo agite 2 gigas de RAM para un archivo de 2 gigabytes, así que, como señala Pasztorpisti , ¡tenemos que lidiar con esos archivos más grandes en trozos!

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

Lo que hemos hecho es que estamos actualizando nuestros hash de este chico malo en trozos de 64 kb a medida que avanzamos con el práctico método de actualización de hashlib . ¡De esta manera usamos mucha menos memoria que los 2 GB que se necesitarían para hash al tipo de una vez!

Puedes probar esto con:

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

¡Espero que ayude!

Además, todo esto se describe en la pregunta vinculada en el lado derecho: Obtenga hash MD5 de archivos grandes en Python


¡Apéndice!

En general, al escribir Python, ayuda a acostumbrarse a seguir pep-8 . Por ejemplo, en Python las variables suelen estar separadas por guiones bajos, no camelCased. Pero eso es solo estilo y nadie realmente se preocupa por esas cosas, excepto las personas que tienen que leer mal estilo ... que podría ser usted leyendo este código dentro de unos años.

Randall Hunt
fuente
@ranman Hola, no pude obtener la parte {0} ". format (sha1.hexdigest ()). ¿Por qué lo usamos en lugar de solo usar sha1.hexdigest ()?
Belial
@Belial ¿Qué no funcionaba? Principalmente lo estaba usando para diferenciar entre los dos hashes ...
Randall Hunt
@ranman Todo está funcionando, simplemente nunca usé esto y no lo he visto en la literatura. "{0}". Formato () ... desconocido para mí. :)
Belial
1
¿Cómo debo elegir BUF_SIZE?
Martin Thoma
1
Esto no genera los mismos resultados que los shasumbinarios. La otra respuesta que se enumera a continuación (la que usa memoryview) es compatible con otras herramientas de hash.
tedivm
61

Para el cálculo correcto y eficiente del valor hash de un archivo (en Python 3):

  • Abra el archivo en modo binario (es decir, agréguelo 'b'al modo de archivo ) para evitar problemas de codificación de caracteres y conversión de final de línea.
  • No lea el archivo completo en la memoria, ya que es una pérdida de memoria. En su lugar, léalo secuencialmente bloque por bloque y actualice el hash para cada bloque.
  • Elimine el doble búfer, es decir, no use E / S en búfer, porque ya usamos un tamaño de bloque óptimo.
  • Úselo readinto()para evitar la agitación del búfer.

Ejemplo:

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
fuente
2
¿Cómo sabe cuál es un tamaño de bloque óptimo?
Mitar
1
@Mitar, un límite inferior es el máximo del bloque físico (tradicionalmente 512 bytes o 4 KB con los discos más nuevos) y el tamaño de la página del sistema (4 KB en muchos sistemas, otras opciones comunes: 8 KB y 64 KiB). Luego, básicamente, realiza una evaluación comparativa y / o mira los resultados de la evaluación comparativa publicados y el trabajo relacionado (por ejemplo, verifique qué uso actual de rsync / GNU cp / ...).
maxschlepzig
¿ resource.getpagesizeSería de alguna utilidad aquí, si quisiéramos intentar optimizarlo de forma algo dinámica? ¿Y sobre qué mmap?
jpmc26
@ jpmc26, getpagesize () no es tan útil aquí - los valores comunes son 4 KiB u 8 KiB, algo en ese rango, es decir, algo mucho más pequeño que 128 KiB - 128 KiB es generalmente una buena opción. mmap no ayuda mucho en nuestro caso de uso, ya que leemos secuencialmente el archivo completo de adelante hacia atrás. mmap tiene ventajas cuando el patrón de acceso es más parecido al de acceso aleatorio, si se accede a las páginas más de una vez y / o si mmap simplifica la gestión del búfer de lectura.
maxschlepzig
3
Evalué tanto la solución de (1) @Randall Hunt como (2) la suya (en este orden, es importante debido a la caché de archivos) con un archivo de alrededor de 116 GB y un algoritmo sha1sum. La solución 1 se modificó para usar un búfer de 20 * 4096 (PAGE_SIZE) y establecer el parámetro de almacenamiento en búfer en 0. Se modificó el algoritmo único de la solución 2 (sha256 -> sha1). Resultado: (1) 3m37.137s (2) 3m30.003s. El sha1sum nativo en modo binario: 3m31.395s
bioinfornatics
18

Yo propondría simplemente:

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

Todas las demás respuestas aquí parecen complicarse demasiado. Python ya está almacenando en búfer al leer (de manera ideal, o configura ese almacenamiento en búfer si tiene más información sobre el almacenamiento subyacente) y, por lo tanto, es mejor leer en fragmentos que la función hash encuentra ideal, lo que lo hace más rápido o al menos menos intensivo en CPU para calcular la función hash. Entonces, en lugar de deshabilitar el almacenamiento en búfer e intentar emularlo usted mismo, utiliza el almacenamiento en búfer de Python y controla lo que debería controlar: lo que el consumidor de sus datos encuentra ideal, el tamaño del bloque hash.

Mitar
fuente
Respuesta perfecta, pero sería bueno si respaldara sus declaraciones con el documento relacionado: Python3 - open () y Python2 - open () . Incluso teniendo en cuenta la diferencia entre ambos, el enfoque de Python3 es más sofisticado. Sin embargo, ¡realmente aprecié la perspectiva centrada en el consumidor!
Murmel
hash.block_sizese documenta como el 'tamaño de bloque interno del algoritmo hash'. Hashlib no lo encuentra ideal . Nada en la documentación del paquete sugiere que update()prefiera hash.block_sizeuna entrada de tamaño. No usa menos CPU si lo llamas así. Su file.read()llamada conduce a muchas creaciones de objetos innecesarios y copias superfluas desde el búfer de archivo a su nuevo objeto de bytes de trozos.
maxschlepzig
Los hash actualizan su estado en block_sizetrozos. Si no los proporciona en esos fragmentos, deben almacenarse en búfer y esperar a que aparezcan suficientes datos, o dividir los datos dados en fragmentos internamente. Entonces, puede manejar eso en el exterior y luego simplificar lo que sucede internamente. Encuentro este ideal. Ver por ejemplo: stackoverflow.com/a/51335622/252025
Mitar
El block_sizees mucho más pequeño que cualquier tamaño útil de lectura. Además, cualquier bloque útil y tamaño de lectura son potencias de dos. Por tanto, el tamaño de lectura es divisible por el tamaño de bloque para todas las lecturas excepto posiblemente la última. Por ejemplo, el tamaño del bloque sha256 es de 64 bytes. Eso significa que update()es capaz de procesar directamente la entrada sin ningún búfer hasta un múltiplo de block_size. Por lo tanto, solo si la última lectura no es divisible por el tamaño del bloque, debe almacenar en búfer hasta 63 bytes, una vez. Por lo tanto, su último comentario es incorrecto y no respalda las afirmaciones que hace en su respuesta.
maxschlepzig
El punto es que uno no tiene que optimizar el almacenamiento en búfer porque ya lo hace Python al leer. Por lo tanto, solo tiene que decidir la cantidad de bucle que desea hacer al aplicar hash sobre ese búfer existente.
Mitar
6

He programado un módulo que puede procesar archivos grandes con diferentes algoritmos.

pip3 install py_essentials

Utilice el módulo así:

from py_essentials import hashing as hs
hash = hs.fileChecksum("path/to/the/file.txt", "sha256")
phyyyl
fuente
¿Es multiplataforma (Linux + Win)? ¿Funciona con Python3? ¿También se mantiene todavía?
Basj
5

Aquí hay una solución POSIX de Python 3 (¡no Windows!) Que se utiliza mmappara mapear el objeto en la 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()
Antti Haapala
fuente
Pregunta ingenua ... ¿cuál es la ventaja de usar mmapen este escenario?
Jonathan B.
1
@JonathanB. la mayoría de los métodos crean innecesariamente bytesobjetos en la memoria y llaman readdemasiadas o muy pocas veces. Esto asignará el archivo directamente a la memoria virtual y lo codificará desde allí: el sistema operativo puede asignar el contenido del archivo directamente desde la memoria caché del búfer al proceso de lectura. Esto significa que esto podría ser más rápido por un factor significativo sobre este
Antti Haapala
@JonathanB. Hice la prueba y la diferencia no es tan significativa en este caso, estamos hablando de ~ 15% sobre el método ingenuo.
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
fuente
2
Básicamente, está haciendo lo echo $USER_INPUT | md5sum > encrypted.txt && cat encrypted.txtque no se ocupa del hash de archivos, especialmente no con los grandes.
Murmel
1
hash! = cifrado
bugmenot123