Generando una suma de comprobación MD5 de un archivo

Resuelto Alexander asked hace 14 años • 9 respuestas

¿Existe alguna forma sencilla de generar (y verificar) sumas de verificación MD5 de una lista de archivos en Python? (Tengo un pequeño programa en el que estoy trabajando y me gustaría confirmar las sumas de verificación de los archivos).

Alexander avatar Aug 08 '10 02:08 Alexander
Aceptado

Puedes usar hashlib.md5()

Tenga en cuenta que a veces no podrá guardar todo el archivo en la memoria. En ese caso, tendrás que leer fragmentos de 4096 bytes secuencialmente y alimentarlos al md5método:

import hashlib
def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

Nota: hash_md5.hexdigest() devolverá la representación de cadena hexadecimal para el resumen, si solo necesita los bytes empaquetados use return hash_md5.digest(), para no tener que volver a convertir.

quantumSoup avatar Aug 07 '2010 19:08 quantumSoup

Hay una manera que es bastante ineficiente en cuanto a memoria .

fila india:

import hashlib
def file_as_bytes(file):
    with file:
        return file.read()

print hashlib.md5(file_as_bytes(open(full_path, 'rb'))).hexdigest()

lista de archivos:

[(fname, hashlib.md5(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

Sin embargo, recuerde que se sabe que MD5 está roto y no debe usarse para ningún propósito, ya que el análisis de vulnerabilidad puede ser realmente complicado y analizar cualquier posible uso futuro que se le pueda dar a su código por problemas de seguridad es imposible. En mi humilde opinión, debería eliminarse por completo de la biblioteca para que todos los que lo utilicen se vean obligados a actualizarlo. Entonces, esto es lo que deberías hacer en su lugar:

[(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

Si solo desea un resumen de 128 bits, puede hacerlo .digest()[:16].

Esto le dará una lista de tuplas, cada tupla contendrá el nombre de su archivo y su hash.

Nuevamente cuestiono fuertemente su uso de MD5. Debería al menos utilizar SHA1 y, dadas las fallas recientes descubiertas en SHA1 , probablemente ni siquiera eso. Algunas personas piensan que mientras no utilices MD5 con fines "criptográficos", estás bien. Pero las cosas tienden a terminar teniendo un alcance más amplio de lo que inicialmente se esperaba, y su análisis casual de vulnerabilidad puede resultar completamente erróneo. Es mejor acostumbrarse a utilizar el algoritmo correcto desde el principio. Es simplemente escribir un grupo diferente de letras, eso es todo. No es tan dificil.

Aquí hay una forma que es más compleja, pero eficiente en cuanto a memoria :

import hashlib

def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
    for block in bytesiter:
        hasher.update(block)
    return hasher.hexdigest() if ashexstr else hasher.digest()

def file_as_blockiter(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block
            block = afile.read(blocksize)


[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.md5()))
    for fname in fnamelst]

Y, nuevamente, dado que MD5 está roto y realmente no debería usarse nunca más:

[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
    for fname in fnamelst]

Nuevamente, puede colocarlo [:16]después de la llamada hash_bytestr_iter(...)si solo desea un resumen de 128 bits.

Omnifarious avatar Aug 07 '2010 19:08 Omnifarious

Claramente no estoy agregando nada fundamentalmente nuevo, pero agregué esta respuesta antes de poder comentar, además las regiones del código aclaran las cosas; de todos modos, específicamente para responder a la pregunta de @Nemo de la respuesta de Omnifarious:

Estaba pensando un poco en las sumas de verificación (vine aquí buscando sugerencias sobre tamaños de bloques, específicamente) y descubrí que este método puede ser más rápido de lo que cabría esperar. Tomando el resultado más rápido (pero bastante típico) timeit.timeitde /usr/bin/timecada uno de varios métodos de suma de comprobación, un archivo de aprox. 11 MB:

$ ./sum_methods.py
crc32_mmap(filename) 0.0241742134094
crc32_read(filename) 0.0219960212708
subprocess.check_output(['cksum', filename]) 0.0553209781647
md5sum_mmap(filename) 0.0286180973053
md5sum_read(filename) 0.0311000347137
subprocess.check_output(['md5sum', filename]) 0.0332629680634
$ time md5sum /tmp/test.data.300k
d3fe3d5d4c2460b5daacc30c6efbc77f  /tmp/test.data.300k

real    0m0.043s
user    0m0.032s
sys     0m0.010s
$ stat -c '%s' /tmp/test.data.300k
11890400

Entonces, parece que tanto Python como /usr/bin/md5sum tardan unos 30 ms para un archivo de 11 MB. La función relevante md5sum( md5sum_readen el listado anterior) es bastante similar a la de Omnifarious:

import hashlib
def md5sum(filename, blocksize=65536):
    hash = hashlib.md5()
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            hash.update(block)
    return hash.hexdigest()

Por supuesto, estos son de ejecuciones únicas (las mmapque siempre son un poco más rápidas cuando se realizan al menos unas pocas docenas de ejecuciones), y la mía generalmente tiene una extra f.read(blocksize)después de que se agota el búfer, pero es razonablemente repetible y muestra que md5sumen la línea de comando está no necesariamente más rápido que una implementación de Python...

EDITAR: Perdón por la gran demora, hace tiempo que no veo esto, pero para responder la pregunta de @EdRandall, escribiré una implementación de Adler32. Sin embargo, no he realizado los puntos de referencia para ello. Es básicamente lo mismo que hubiera sido el CRC32: en lugar de las llamadas init, update y digest, todo es una zlib.adler32()llamada:

import zlib
def adler32sum(filename, blocksize=65536):
    checksum = zlib.adler32("")
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            checksum = zlib.adler32(block, checksum)
    return checksum & 0xffffffff

Tenga en cuenta que esto debe comenzar con la cadena vacía, ya que las sumas de Adler difieren cuando comienzan desde cero versus su suma para "", que es 1: CRC puede comenzar 0en su lugar. El AND-ing es necesario para convertirlo en un entero sin signo de 32 bits, lo que garantiza que devuelva el mismo valor en todas las versiones de Python.

rsandwick3 avatar Feb 04 '2014 23:02 rsandwick3