¿Hay alguna forma simple 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).
Puede usar hashlib.md5 ()
Tenga en cuenta que a veces no podrá ajustar todo el archivo en la memoria. En ese caso, tendrá que leer fragmentos de 4096 bytes secuencialmente y alimentarlos al md5
mé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 el uso de bytes empaquetados return hash_md5.digest()
, para que no tenga que volver a convertir.
Hay una manera que es bastante ineficiente de memoria .
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]
Recordemos, sin embargo, que MD5 se sabe roto y no debe ser utilizado para cualquier propósito ya que el análisis de la vulnerabilidad puede ser muy difícil, y el análisis de cualquier posible uso futuro de su código podría ser condenado a las cuestiones de seguridad es imposible. En mi humilde opinión, debe eliminarse completamente de la biblioteca para que todos los que lo usen se vean obligados a actualizar. Entonces, esto es lo que debes hacer en su lugar:
[(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]
Si solo quieres 128 bits de digestión, puedes hacerlo .digest()[:16]
.
Esto le dará una lista de tuplas, cada tupla contiene el nombre de su archivo y su hash.
Nuevamente, cuestiono mucho su uso de MD5. Al menos debería usar SHA1, y dados los defectos recientes descubiertos en SHA1 , probablemente ni siquiera eso. Algunas personas piensan que mientras no esté usando MD5 para propósitos 'criptográficos', está bien. Pero las cosas tienden a tener un alcance más amplio de lo que inicialmente esperaba, y su análisis de vulnerabilidad casual puede resultar completamente defectuoso. Es mejor acostumbrarse a usar el algoritmo correcto desde el principio. Simplemente escribir un grupo diferente de letras es todo. No es tan dificil.
Aquí hay una manera que es 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, de nuevo, dado que MD5 está roto y ya no debería usarse nunca más:
[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
for fname in fnamelst]
Una vez más, puede poner [:16]
después de la llamada a hash_bytestr_iter(...)
si solo desea un resumen de 128 bits.
Claramente, no agrego nada fundamentalmente nuevo, pero agregué esta respuesta antes de comentar el estado, además de que las regiones de 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 comprobación (vine aquí buscando sugerencias sobre tamaños de bloque, específicamente), y descubrí que este método puede ser más rápido de lo que cabría esperar. Tomando el más rápido (pero bastante típico) timeit.timeit
o el /usr/bin/time
resultado de cada uno de varios métodos de suma de verificació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
Por lo tanto, parece que tanto Python como / usr / bin / md5sum tardan unos 30 ms en un archivo de 11 MB. La md5sum
función relevante ( md5sum_read
en la lista 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()
De acuerdo, estos son de ejecuciones individuales (las mmap
que siempre son un poco más rápidas cuando se realizan al menos unas pocas docenas de ejecuciones), y la mía generalmente tiene un extra f.read(blocksize)
después de que se agota el búfer, pero es razonablemente repetible y muestra que md5sum
en la línea de comando está no necesariamente más rápido que una implementación de Python ...
EDITAR: Perdón por el largo retraso, no he visto esto en algún 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 habría 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 realmente difieren al comenzar desde cero versus su suma para ""
, que es 1
- CRC puede comenzar con su 0
lugar. losAND
-ing es necesario para convertirlo en un entero sin signo de 32 bits, lo que garantiza que devuelve el mismo valor en todas las versiones de Python.
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
Considere usar en hashlib.blake2b
lugar de md5
(solo reemplace md5
con blake2b
en el fragmento anterior). Es criptográficamente seguro y más rápido que MD5.
:=
operador es un "operador de asignación" (nuevo en Python 3.8+); le permite asignar valores dentro de una expresión más grande; Más información aquí: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()
Creo que confiar en el paquete invoke y el binario md5sum es un poco más conveniente que el subproceso o el paquete md5
import invoke
def get_file_hash(path):
return invoke.Context().run("md5sum {}".format(path), hide=True).stdout.split(" ")[0]
Por supuesto, esto supone que tiene invoke y md5sum instalados.
path
es una ruta proporcionada por el usuario, esto permitirá que cualquier usuario ejecute comandos bash arbitrarios en su sistema.
md5sum
?md5sum
. Es por eso que los programadores conscientes de la seguridad no deberían usarlo en mi opinión.md5sum
Deben evitarse ambas y la técnica descrita en esta pregunta SO: es mejor usar SHA-2 o SHA-3, si es posible: en.wikipedia.org/wiki/Secure_Hash_Algorithms