397 votes

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 (j'ai un petit programme sur lequel je travaille, et j'aimerais confirmer les sommes de contrôle des fichiers).

3 votes

Pourquoi ne pas simplement utiliser md5sum ?

108 votes

Le fait de le garder en Python facilite la gestion de la compatibilité multiplateforme.

0 votes

Si vous voulez une solution avec "barre de progression* ou similaire (pour les très gros fichiers), considérez cette solution : stackoverflow.com/questions/1131220/

568voto

quantumSoup Points 6565

Vous pouvez utiliser hashlib.md5()

Notez que, parfois, vous ne serez pas en mesure de faire tenir tout le fichier en mémoire. Dans ce cas, vous devrez lire des morceaux de 4096 octets séquentiellement et les envoyer à la fonction md5 méthode :

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()

Note : hash_md5.hexdigest() retournera le chaîne hexagonale pour le condensé, si vous avez juste besoin des octets emballés, utilisez return hash_md5.digest() pour que vous n'ayez pas à reconvertir.

321voto

Omnifarious Points 25666

Il y a une façon qui est assez mémorable inefficace .

fichier unique :

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

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

liste de fichiers :

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

Rappelons toutefois que MD5 est connu pour être cassé et ne devrait pas être utilisé à quelque fin que ce soit, car l'analyse des vulnérabilités peut être vraiment délicate, et l'analyse de toute utilisation future possible de votre code pour des questions de sécurité est impossible. IMHO, il devrait être carrément retiré de la bibliothèque afin que tous ceux qui l'utilisent soient obligés de le mettre à jour. Voici donc 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 tuples, chaque tuple contenant le nom de son fichier et son hachage.

Encore une fois, je remets fortement en question votre utilisation du MD5. Vous devriez au moins utiliser SHA1, et étant donné failles récentes découvertes dans SHA1 et probablement même pas. Certaines personnes pensent que tant que vous n'utilisez pas MD5 à des fins "cryptographiques", tout va 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 erronée. Il est préférable de prendre l'habitude d'utiliser le bon algorithme dès le départ. Il s'agit simplement de taper un autre groupe de lettres. Ce n'est pas si difficile.

Voici une méthode plus complexe, mais Mémoire efficace :

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, puisque MD5 est cassé et ne devrait plus jamais ê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 ne voulez que 128 bits de condensé.

71 votes

Je n'utilise le MD5 que pour confirmer que le fichier n'est pas corrompu. Je ne m'inquiète pas tant qu'il soit cassé.

92 votes

@TheLifelessOne : Et malgré les avertissements effrayants de @Omnifarious, c'est une utilisation parfaitement correcte du MD5.

22 votes

@GregS, @TheLifelessOne - Oui, et ensuite quelqu'un trouve un moyen d'utiliser ce fait sur votre application pour qu'un fichier soit accepté comme non corrompu alors que ce n'est pas du tout le fichier que vous attendez. Non, je m'en tiens à mes avertissements effrayants. Je pense que MD5 devrait être supprimé ou accompagné d'avertissements de dépréciation.

35voto

rsandwick3 Points 91

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

J'ai réfléchi un peu aux sommes de contrôle (je suis venu ici pour chercher des suggestions sur la taille des blocs, en particulier), et j'ai découvert que cette méthode peut être plus rapide que vous ne le pensez. En prenant la méthode la plus rapide (mais assez typique) timeit.timeit ou /usr/bin/time résultat de chacune des différentes méthodes de vérification d'un fichier d'environ 11 Mo :

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

Il semble donc que Python et /usr/bin/md5sum prennent environ 30 ms pour un fichier de 11 Mo. Les données pertinentes md5sum fonction ( md5sum_read dans la liste ci-dessus) est assez similaire à celui 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()

Il est vrai qu'il s'agit de séries uniques (les mmap sont toujours un peu plus rapides lorsqu'on en fait au moins quelques dizaines), et le mien a généralement un peu plus de f.read(blocksize) après que le tampon soit épuisé, mais c'est raisonnablement répétable et cela montre que md5sum en ligne de commande n'est pas nécessairement plus rapide qu'une implémentation Python...

EDIT : Désolé pour ce long délai, je n'ai pas regardé cela depuis un certain temps, mais pour répondre à la question de @EdRandall, je vais écrire une implémentation Adler32. Cependant, je n'ai pas encore effectué de tests pour cette implémentation. C'est essentiellement la même chose que ce que le CRC32 aurait été : à la place des appels init, update et digest, tout est un appel à zlib.adler32() appeler :

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 d'Adler diffèrent en effet lorsqu'elles commencent à partir de zéro par rapport à leur somme pour "" qui est 1 -- Le CRC peut commencer par 0 à la place. Le site AND -est nécessaire pour en faire un entier non signé de 32 bits, ce qui garantit qu'il renvoie la même valeur dans toutes les versions de Python.

0 votes

Pourriez-vous éventuellement ajouter quelques lignes comparant SHA1, et aussi zlib.adler32 peut-être ?

1 votes

@EdRandall : adler32 ne vaut vraiment pas la peine d'être utilisé, par exemple. leviathansecurity.com/blog/analysis-of-adler32

14voto

Boris Points 1440

En Python 3.8+ vous pouvez faire

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

Envisagez d'utiliser hashlib.blake2b au lieu de md5 (il suffit de remplacer md5 avec blake2b dans l'extrait ci-dessus). C'est une sécurité cryptographique et plus rapide que MD5.

2 votes

Le site := est un "opérateur d'affectation" (nouveau dans Python 3.8+) ; il vous permet d'affecter des valeurs à l'intérieur d'une expression plus large ; plus d'informations ici : docs.python.org/3/whatsnew/3.8.html#assignment-expressions

12voto

johnson Points 630
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()

4 votes

Bonjour ! Veuillez ajouter quelques explications à votre code pour expliquer pourquoi il s'agit d'une solution au problème. De plus, ce post est assez ancien, donc vous devriez également ajouter des informations sur la raison pour laquelle votre solution ajoute quelque chose que les autres n'ont pas encore abordé.

5 votes

C'est un autre moyen inefficace pour la mémoire

1 votes

Solution à une ligne. Parfait pour quelques essais !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X