Obtener hash MD5 de archivos de gran tamaño en Python
Pregunta
He utilizado hashlib (que sustituye MD5 en Python 2.6 / 3.0) y funcionó bien si abría un archivo y poner su contenido en función hashlib.md5()
.
El problema es con archivos muy grandes que su tamaño pudieran superar el tamaño de RAM.
¿Cómo obtener el hash MD5 de un archivo sin tener que cargar todo el archivo a la memoria?
Solución
Romper el archivo en trozos de 128 bytes y alimentarlos a MD5 consecutivamente usando update()
.
Esto se aprovecha del hecho de que MD5 tiene 128 bytes a digerir los bloques. Básicamente, cuando MD5 digest()
s el archivo, esto es exactamente lo que está haciendo.
Si se asegura a liberar la memoria en cada iteración (es decir, no leer todo el archivo a la memoria), esto tomará no más de 128 bytes de memoria.
Un ejemplo es leer los trozos de este modo:
f = open(fileName)
while not endOfFile:
f.read(128)
Otros consejos
Es necesario leer el archivo en trozos de tamaño adecuado:
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()
NOTA: Asegúrese de abrir el archivo con el 'rb' a la intemperie - de lo contrario obtendrá un resultado erróneo
.Así que hacer todo el lote en un método - usar algo como:
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()
La actualización anterior se basó en las observaciones formuladas por Frerich Raabe - y Probé esto y nos pareció que ser correcto en mi instalación de Python 2.7.2 ventanas
I cotejará los resultados utilizando la herramienta 'Jacksum'.
jacksum -a md5 <filename>
si se preocupan más Pythonic (no 'while True') forma de leer el archivo de comprobar este código:
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()
Tenga en cuenta que el iter () func necesita una cadena de bytes vacía para el iterador vuelto a detener al EOF, ya que read () devuelve b '' (no sólo '').
Esta es mi versión del método de @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()
El uso de múltiples comentarios / respuestas en este tema, aquí está mi solución:
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()
- Se trata de "Pythonic"
- Se trata de una función
- Evita valores implícitos: siempre se prefieren los explícitos .
- Permite (muy importante) performances optimizaciones
Y, por último,
-. Esto ha sido construido por una comunidad, gracias a todos por sus consejos / ideas
A Python 2/3 solución portátil
Para calcular una suma de comprobación (MD5, SHA1, etc.), debe abrir el archivo en modo binario, porque vas a sumar valores bytes:
Para ser py27 / AP3 portátil, usted debe utilizar los paquetes io
, como esto:
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
Si los archivos son grandes, es posible que prefiera para leer el archivo por trozos para evitar almacenar todo el contenido del archivo en la memoria:
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
El truco aquí es usar la función iter()
con un < em> centinela (la cadena vacía).
El iterador creada en este caso llamará o [la función lambda] sin argumentos para cada llamada a su método
next()
; si el valor devuelto es igual a centinela, será levantadoStopIteration
, de lo contrario será devuelto el valor.
Si los archivos son realmente grande, es posible que también es necesario para mostrar información de progreso. Puede hacerlo llamando a una función de devolución de llamada que imprime o registra la cantidad de bytes calculados:
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
Una remezcla de código Bastien Semene que tome comentario Hawkwing acerca de la función hash genérico en cuenta ...
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
u no puede conseguirlo es MD5 sin contenido completo de lectura. pero u puede utilizar actualización función para leer los archivos de bloque de contenido por bloque.
m.update (a); m.update (b) es equivalente a m.update (a + b)
Creo que el siguiente código es más Pythonic:
from hashlib import md5
def get_md5(fname):
m = md5()
with open(fname, 'rb') as fp:
for chunk in fp:
m.update(chunk)
return m.hexdigest()
Implementación de respuesta aceptada para Django:
import hashlib
from django.db import models
class MyModel(models.Model):
file = models.FileField() # any field based on django.core.files.File
def get_hash(self):
hash = hashlib.md5()
for chunk in self.file.chunks(chunk_size=8192):
hash.update(chunk)
return hash.hexdigest()
No me gustan los bucles. Basado en @ Nathan Feger:
md5 = hashlib.md5()
with open(filename, 'rb') as f:
functools.reduce(lambda _, c: md5.update(c), iter(lambda: f.read(md5.block_size * 128), b''), None)
md5.hexdigest()
import hashlib,re
opened = open('/home/parrot/pass.txt','r')
opened = open.readlines()
for i in opened:
strip1 = i.strip('\n')
hash_object = hashlib.md5(strip1.encode())
hash2 = hash_object.hexdigest()
print hash2
No estoy seguro de que no es un poco demasiado alboroto por aquí. Recientemente he tenido problemas con MD5 y archivos almacenados como manchas en MySQL por lo que experimentaron con diferentes tamaños de archivo y el enfoque Python sencilla, a saber:
FileHash=hashlib.md5(FileData).hexdigest()
pude detectar ninguna diferencia notable en el rendimiento con una amplia gama de tamaños de archivo 2Kb a 20Mb y por lo tanto no hay necesidad de 'trozo' el hash. De todos modos, si Linux tiene que ir en el disco, es probable que lo haga al menos tan bien como la capacidad del programador medio para evitar que hacerlo. Como sucedió, el problema había nada que ver con MD5. Si estás usando MySQL, no se olvide el MD5 () y sha1 () funciones ya allí.