python - example - Generando una suma de comprobación MD5 de un archivo
python hash function (3)
Claramente, no estoy agregando nada fundamentalmente nuevo, pero agregué esta respuesta antes de estar en condiciones de comentar el estado :-), más las regiones del código hacen las cosas más claras, de todos modos, específicamente para responder a la pregunta de @Nemo de la respuesta de Omnifarious:
Resulta que pensaba un poco en las sumas de comprobación (vine aquí en busca de sugerencias sobre tamaños de bloques, específicamente), y descubrí que este método puede ser más rápido de lo que se esperaba. Tomando el resultado de timeit.timeit
o /usr/bin/time
más rápido (pero bastante típico) de cada uno de los varios métodos de suma de comprobación de un archivo de aprox. 11MB:
$ ./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 toman unos 30ms para un archivo de 11MB. La función md5sum
relevante ( md5sum_read
en 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, estas son de corridas simples (las de mmap
son siempre un poco más rápidas cuando se realizan al menos unas pocas docenas de ejecuciones), y las mías generalmente tienen un f.read(blocksize)
adicional f.read(blocksize)
vez que se agota el búfer, pero es bastante repetible y muestra que md5sum
en la línea de comandos no es necesariamente más rápido que una implementación de Python ...
EDITAR: Perdón por el largo retraso, no he mirado esto en mucho tiempo, pero para responder a la pregunta de @ EdRandall, escribiré una implementación de Adler32. Sin embargo, no he ejecutado los puntos de referencia para ello. Básicamente es lo mismo que el CRC32: en lugar de las llamadas init, update y digest, todo es una llamada zlib.adler32()
:
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 de hecho al comenzar desde cero en comparación con su suma para ""
, que es 1
- CRC puede comenzar con 0
lugar. El AND
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.
¿Hay alguna forma sencilla de generar (y verificar) las sumas de comprobació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 comprobación de los archivos).
Hay una manera en que la memoria es bastante ineficiente .
archivo único:
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, se sabe que el MD5 está roto y (IMHO) debe venir con advertencias de desaprobación y ser eliminado de la biblioteca, así que aquí hay cómo hacerlo:
[(fname, hashlib.sha256(file_as_bytes(open(fname, ''rb''))).digest()) for fname in fnamelst]
Si solo quieres un resumen de 128 bits, puedes hacer .digest()[:16]
.
Esto le dará una lista de tuplas, cada una con el nombre de su archivo y su hash.
Una vez más cuestiono fuertemente su uso de MD5. Debes estar al menos usando SHA1. Algunas personas piensan que mientras no utilices MD5 para propósitos ''criptográficos'', estás bien. Pero las cosas tienden a ser más amplias de lo que inicialmente se esperaba, y su análisis de vulnerabilidad casual puede resultar completamente erróneo. Es mejor simplemente acostumbrarse a usar el algoritmo correcto para salir de la puerta. Es solo escribir un montón diferente de letras es todo. No es tan dificil.
Aquí hay una forma más compleja, pero eficiente en 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, ya que el MD5 está roto y ya no se debe usar nunca más:
[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, ''rb'')), hashlib.sha256()))
for fname in fnamelst]
Nuevamente, puede poner [:16]
después de la llamada a hash_bytestr_iter(...)
si solo quiere un resumen de 128 bits.
Puedes usar hashlib.md5()
Tenga en cuenta que a veces no podrá guardar todo el archivo en la memoria. En ese caso, deberás leer fragmentos de 4096 bytes de forma secuencial y enviarlos a la función Md5:
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 que los bytes empaquetados utilicen el return hash_md5.digest()
, para que no tenga que volver a realizar la conversión.