web-dev-qa-db-fra.com

Générer une somme de contrôle MD5 d'un fichier

Existe-t-il un moyen simple de générer (et de vérifier) ​​les sommes de contrôle MD5 d’une liste de fichiers en Python? (Je travaille sur un petit programme et j'aimerais confirmer les sommes de contrôle des fichiers).

290
Alexander

Vous pouvez utiliser hashlib.md5 ()

Notez que parfois vous ne pourrez pas mettre tout le fichier en mémoire. Dans ce cas, vous devrez lire les morceaux de 4096 octets de manière séquentielle et les alimenter à la fonction 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()

Remarque: hash_md5.hexdigest() renverra la représentation chaîne hexadécimale du résumé, si vous venez besoin des octets condensés, utilisez return hash_md5.digest(), vous n’avez donc pas à reconvertir.

385
quantumSoup

Il existe un moyen qui rend la mémoire inefficace .

un seul fichier:

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

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

liste des fichiers:

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

Rappelez-vous cependant que MD5 est connu sous le nom de cassé et ne devrait pas être utilisé à quelque fin que ce soit, car l'analyse de vulnérabilité peut être très délicate, et il est impossible d'analyser toute utilisation future possible de votre code pour des problèmes de sécurité. IMHO, il devrait être complètement retiré de la bibliothèque afin que tous ceux qui l'utilisent soient obligés de mettre à jour. Alors, voici ce que vous devriez faire à la place:

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

Si vous voulez seulement 128 bits de résumé, vous pouvez faire .digest()[:16].

Cela vous donnera une liste de n-uplets, chaque tuple contenant le nom de son fichier et son hachage.

Encore une fois, je mets fortement en doute votre utilisation du MD5. Vous devriez au moins utiliser SHA1, et étant donné failles récemment découvertes dans SHA1 , probablement même pas. Certaines personnes pensent que tant que vous n'utilisez pas MD5 à des fins "cryptographiques", vous vous en sortez bien. Mais les choses ont tendance à avoir une portée plus large que ce à quoi vous vous attendiez initialement, et votre analyse de vulnérabilité occasionnelle peut s'avérer complètement imparfaite. Il est préférable de prendre l'habitude d'utiliser le bon algorithme en dehors de la grille. Il suffit de taper un tas de lettres différentes, c'est tout. Ce n'est pas si dur.

Voici un moyen plus complexe, mais efficace en mémoire :

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]

Et, encore une fois, depuis que MD5 est cassé et ne devrait plus être utilisé:

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

Encore une fois, vous pouvez mettre [:16] après l'appel à hash_bytestr_iter(...) si vous voulez seulement une valeur de 128 bits.

290
Omnifarious

Je n'ajoute clairement rien de fondamentalement nouveau, mais j'ai ajouté cette réponse avant de pouvoir commenter, plus les régions de code rendent les choses plus claires - de toute façon, spécifiquement pour répondre à la question de @ Nemo de Omnifarious:

Je pensais un peu aux sommes de contrôle (je suis venu ici à la recherche de suggestions sur la taille des blocs, en particulier), et j'ai constaté que cette méthode était plus rapide que prévu. Prendre le plus rapide (mais assez typique) timeit.timeit ou /usr/bin/time résulte de chacune des différentes méthodes de contrôle d’un fichier d’env. 11Mo:

$ ./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

Donc, on dirait que Python et/usr/bin/md5sum prennent environ 30 ms pour un fichier de 11 Mo. La fonction md5sum pertinente (md5sum_read dans la liste ci-dessus) est assez similaire à celle d'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()

Certes, il s’agit de passages uniques (les mmap sont toujours plus petits quand on en compte au moins quelques dizaines), et le mien a généralement un extra f.read(blocksize) après épuisement de la mémoire tampon, mais c’est raisonnablement reproductible et montre que md5sum sur la ligne de commande n'est pas nécessairement plus rapide qu'une implémentation Python ...

EDIT: Désolé pour le long délai, je n’ai pas examiné cela depuis un moment, mais pour répondre à la question de @ EdRandall, je vais écrire une implémentation d’Adler32. Cependant, je n'ai pas fait les tests pour cela. C'est fondamentalement la même chose que le CRC32 aurait été: au lieu d'appels init, update et digest, tout est un appel 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

Notez que cela doit commencer par la chaîne vide, car les sommes Adler diffèrent lorsque vous démarrez à partir de zéro par rapport à leur somme pour "", qui est 1 - CRC peut commencer par 0 . La ANDing est nécessaire pour en faire un entier non signé 32 bits, ce qui garantit qu'il retourne la même valeur sur toutes les versions de Python.

31
rsandwick3