Obtenga el hash MD5 de archivos grandes en Python

4 minutos de lectura

Avatar de usuario de JustRegisterMe
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?

  • 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ó el file_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

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 hacia open función.

    – Frerich Raabe

    21 de julio de 2011 a las 13:02

  • Esta es una adición simple, pero usando hexdigest en vez de digest 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

Avatar de usuario de Yuval Adam
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 de md5. 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

Avatar de usuario de Piotr Czapla
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

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 de 8192.

    – 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

Avatar de usuario de Nathan Feger
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()

Avatar de usuario de Bastien Semene
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

Avatar de usuario de Peter Mortensen
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

Avatar de usuario de Peter Mortensen
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

¿Ha sido útil esta solución?