SoloRegístrate
he utilizado hashlib (que reemplaza md5 en Python 2.6/3.0), y funcionaba bien si abría un archivo y ponía su contenido en el hashlib.md5()
función.
El problema es con archivos muy grandes que sus tamaños pueden exceder el tamaño de RAM.
¿Cómo puedo obtener el hash MD5 de un archivo sin cargar todo el archivo en la memoria?
Debe leer el archivo en fragmentos 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 su archivo con la ‘rb’ abierta; de lo contrario, obtendrá un resultado incorrecto.
Entonces, para hacer todo en un solo método, use 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 los comentarios proporcionados por Frerich Raabe, probé esto y descubrí que era correcto en mi instalación de Windows Python 2.7.2.
Cotejé los resultados usando el jacksum herramienta.
jacksum -a md5 <filename>
-
Lo que es importante notar es que el archivo que se pasa a esta función debe abrirse en modo binario, es decir, pasando
rb
haciaopen
función.– Frerich Raabe
21 de julio de 2011 a las 13:02
-
Esta es una adición simple, pero usando
hexdigest
en vez dedigest
producirá un hash hexadecimal que “se parece” a la mayoría de los ejemplos de hash.– tchaymore
16 de octubre de 2011 a las 2:26
-
¿No debería ser
if len(data) < block_size: break
?– Erik Kaplún
2 de noviembre de 2012 a las 10:35
-
Erik, no, ¿por qué sería? El objetivo es enviar todos los bytes a MD5, hasta el final del archivo. Obtener un bloque parcial no significa que no se deban alimentar todos los bytes a la suma de comprobación.
– usuario25148
2 de noviembre de 2012 a las 20:12
-
@usuario2084795
open
siempre abre un identificador de archivo nuevo con la posición establecida al inicio del archivo, (a menos que abra un archivo para agregar).–Steve Barnes
5 de julio de 2017 a las 9:15
adán yuval
Divida el archivo en fragmentos de 8192 bytes (o algún otro múltiplo de 128 bytes) y aliméntelos a MD5 consecutivamente usando update()
.
Esto aprovecha el hecho de que MD5 tiene bloques de resumen de 128 bytes (8192 es 128 × 64). Dado que no está leyendo el archivo completo en la memoria, esto no usará mucho más de 8192 bytes de memoria.
En Python 3.8+ puedes hacer
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
-
Puede usar con la misma eficacia un tamaño de bloque de cualquier múltiplo de 128 (por ejemplo, 8192, 32768, etc.) y eso será mucho más rápido que leer 128 bytes a la vez.
– jmanning2k
15 de julio de 2009 a las 15:09
-
Gracias jmanning2k por esta nota importante, una prueba en un archivo de 184 MB toma (0m9.230s, 0m2.547s, 0m2.429s) usando (128, 8192, 32768), usaré 8192 ya que el valor más alto da un efecto no perceptible.
– JustRegisterMe
17 de julio de 2009 a las 19:33
-
Si puedes, deberías usar
hashlib.blake2b
en vez demd5
. A diferencia de MD5, BLAKE2 es seguro, y es aún más rápido.– Boris Verjovskiy
22 de noviembre de 2019 a las 11:59
-
@Boris, en realidad no puedes decir que BLAKE2 es seguro. Todo lo que puedes decir es que aún no se ha roto.
– vy32
8 de abril de 2020 a las 14:57
-
@ vy32 tampoco puedes decir que definitivamente se romperá. Lo veremos en 100 años, pero al menos es mejor que MD5, que definitivamente es inseguro.
– Boris Verjovskiy
08/04/2020 a las 15:50
Piotr Czapla
Pitón
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
h.update(chunk)
return h.digest()
Python 3.8 y superior
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
while chunk := f.read(chunk_num_blocks*h.block_size):
h.update(chunk)
return h.digest()
Publicación original
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
h.update(chunk)
return h.digest()
import hashlib
def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
h = hash_factory()
with open(filename,'rb') as f:
while chunk := f.read(chunk_num_blocks*h.block_size):
h.update(chunk)
return h.digest()
Si quieres una más Pythonic (no while True
) forma de leer el archivo, verifique 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()
La función necesita una cadena de bytes vacía para que el iterador devuelto se detenga en EOF, ya que read()
devoluciones b''
(No solo ''
).
-
Mejor aún, usa algo como
128*md5.block_size
en vez de8192
.– mrkj
6 de enero de 2011 a las 22:51
-
mrkj: Creo que es más importante elegir el tamaño del bloque de lectura en función de su disco y luego asegurarse de que sea un múltiplo de
md5.block_size
.– Harvey
12 de abril de 2013 a las 14:10
-
la
b''
la sintaxis era nueva para mí. Explicado aquí.– cod3monk3y
18 de febrero de 2014 a las 5:19
-
@ThorSummoner: No realmente, pero a partir de mi trabajo para encontrar tamaños de bloque óptimos para la memoria flash, sugeriría elegir un número como 32k o algo fácilmente divisible por 4, 8 o 16k. Por ejemplo, si el tamaño de su bloque es de 8k, la lectura de 32k será de 4 lecturas con el tamaño de bloque correcto. Si es 16, entonces 2. Pero en cada caso, estamos bien porque estamos leyendo un número entero múltiplo de bloques.
– Harvey
16 de marzo de 2015 a las 14:21
-
“while True” es bastante pitónico.
– Jürgen A. Erhard
16 de diciembre de 2015 a las 9:07
nathan feger
Aquí está 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()
bastien semene
Usando múltiples comentarios/respuestas para esta pregunta, 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()
- Esto es pitónico
- Esta es una función
- Evita los valores implícitos: prefiere siempre los explícitos.
- Permite optimizaciones de rendimiento (muy importantes)
-
Una sugerencia: haga que su objeto md5 sea un parámetro opcional de la función para permitir funciones hash alternativas, como sha256 para reemplazar fácilmente MD5. También propondré esto como una edición.
– Ala de halcón
15/08/2013 a las 19:41
-
también: el resumen no es legible por humanos. hexdigest() permite una salida más comprensible y comúnmente reconocible, así como un intercambio más fácil del hash
– Ala de halcón
15/08/2013 a las 19:51
-
Otros formatos hash están fuera del alcance de la pregunta, pero la sugerencia es relevante para una función más genérica. Agregué una opción “legible por humanos” de acuerdo con su segunda sugerencia.
-Bastien Semene
27 de agosto de 2013 a las 8:17
-
¿Puede explicar cómo funciona ‘hr’ aquí?
– EnemyBagJones
23 de marzo de 2018 a las 18:19
-
@EnemyBagJones ‘hr’ significa legible por humanos. Devuelve una cadena de dígitos hexadecimales de 32 caracteres de longitud: docs.python.org/2/library/md5.html#md5.md5.hexdigest
-Bastien Semene
27 de marzo de 2018 a las 9:46
Pedro Mortensen
Una solución portátil de Python 2/3
Para calcular una suma de comprobación (md5, sha1, etc.), debe abrir el archivo en modo binario, porque sumará valores de bytes:
Para ser portátil Python 2.7 y Python 3, debe usar el io
paquetes, así:
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 sus archivos son grandes, es posible que prefiera leer el archivo por partes 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 el iter()
funcionar con un centinela (la cadena vacía).
El iterador creado en este caso llamará o [the lambda function] sin argumentos para cada llamada a su
next()
método; si el valor devuelto es igual a centinela,StopIteration
se elevará, de lo contrario se devolverá el valor.
Si sus archivos son De Verdad grande, es posible que también deba 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 sugerencia: haga que su objeto md5 sea un parámetro opcional de la función para permitir funciones hash alternativas, como sha256 para reemplazar fácilmente MD5. También propondré esto como una edición.
– Ala de halcón
15/08/2013 a las 19:41
-
también: el resumen no es legible por humanos. hexdigest() permite una salida más comprensible y comúnmente reconocible, así como un intercambio más fácil del hash
– Ala de halcón
15/08/2013 a las 19:51
-
Otros formatos hash están fuera del alcance de la pregunta, pero la sugerencia es relevante para una función más genérica. Agregué una opción “legible por humanos” de acuerdo con su segunda sugerencia.
-Bastien Semene
27 de agosto de 2013 a las 8:17
-
¿Puede explicar cómo funciona ‘hr’ aquí?
– EnemyBagJones
23 de marzo de 2018 a las 18:19
-
@EnemyBagJones ‘hr’ significa legible por humanos. Devuelve una cadena de dígitos hexadecimales de 32 caracteres de longitud: docs.python.org/2/library/md5.html#md5.md5.hexdigest
-Bastien Semene
27 de marzo de 2018 a las 9:46
Pedro Mortensen
Una remezcla del código de Bastien Semene que tiene en cuenta el comentario de Hawkwing sobre la función hash genérica…
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
Yo reformularía: “¿Cómo obtener el MD5 de un archivo sin cargar todo el archivo en la memoria?”
– XTL
24 de febrero de 2012 a las 12:29
comenzando con 3.11
hashlib
ganó elfile_digest
función que parece tomar la molestia de escribir fragmentos repetitivos de usted docs.python.org/3.11/library/hashlib.html#hashlib.file_digest– psyfert
8 de noviembre a las 14:10