196 votes

Obtenir le hachage MD5 de gros fichiers en Python

J'ai utilisé hashlib (qui remplace md5 dans Python 2.6/3.0) et cela a bien fonctionné si j'ai ouvert un fichier et mis son contenu dans hashlib.md5() fonction.

Le problème avec les très gros fichiers est que leur taille peut dépasser celle de la RAM.

Comment obtenir le hachage MD5 d'un fichier sans charger le fichier entier en mémoire ?

22 votes

Je reformulerais : "Comment obtenir le has MD5 d'un fichier sans charger tout le fichier en mémoire ?".

223voto

Lars Wirzenius Points 12197

Vous devez lire le fichier par morceaux de taille appropriée :

def md5_for_file(f, block_size=2**20):
    md5 = hashlib.md5()
    while True:
        data = f.read(block_size)
        if not data:
            break
        md5.update(data)
    return md5.digest()

NOTE : Assurez-vous que vous ouvrez votre fichier avec le 'rb' à l'ouverture - sinon vous obtiendrez le mauvais résultat.

Donc, pour faire le tout en une seule méthode - utilisez quelque chose comme :

def generate_file_md5(rootdir, filename, blocksize=2**20):
    m = hashlib.md5()
    with open( os.path.join(rootdir, filename) , "rb" ) as f:
        while True:
            buf = f.read(blocksize)
            if not buf:
                break
            m.update( buf )
    return m.hexdigest()

La mise à jour ci-dessus est basée sur les commentaires fournis par Frerich Raabe. Je l'ai testée et je l'ai trouvée correcte sur mon installation Windows de Python 2.7.2.

J'ai vérifié les résultats à l'aide de l'outil "jacksum".

jacksum -a md5 <filename>

http://www.jonelo.de/java/jacksum/

29 votes

Ce qu'il est important de noter, c'est que le fichier qui est passé à cette fonction doit être ouvert en mode binaire, c'est-à-dire en passant la commande rb à la open fonction.

11 votes

Il s'agit d'un simple ajout, mais en utilisant hexdigest au lieu de digest produira un hachage hexadécimal qui "ressemble" à la plupart des exemples de hachage.

0 votes

Cela ne devrait-il pas être if len(data) < block_size: break ?

168voto

Yuval Adam Points 59423

Décomposez le fichier en morceaux de 8192 octets (ou tout autre multiple de 128 octets) et envoyez-les consécutivement à MD5 en utilisant update() .

Cela permet de tirer parti du fait que le MD5 possède des blocs de résumé de 128 octets (8192 est 128×64). Comme vous ne lisez pas l'intégralité du fichier en mémoire, cette opération n'utilisera pas beaucoup plus de 8192 octets de mémoire.

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

83 votes

Vous pouvez tout aussi bien utiliser une taille de bloc de n'importe quel multiple de 128 (disons 8192, 32768, etc.) et cela sera beaucoup plus rapide que de lire 128 octets à la fois.

41 votes

Merci jmanning2k pour cette note importante, un test sur un fichier de 184MB prend (0m9.230s, 0m2.547s, 0m2.429s) en utilisant (128, 8192, 32768), je vais utiliser 8192 car la valeur supérieure donne un effet non perceptible.

0 votes

Si vous le pouvez, vous devriez utiliser hashlib.blake2b au lieu de md5 . Contrairement à MD5, BLAKE2 est sécurisé, et c'est encore plus rapide.

113voto

Piotr Czapla Points 8626

J'ai incorporé ci-dessous les suggestions des commentaires. Merci à tous !

python < 3.7

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''): 
            h.update(chunk)
    return h.digest()

python 3.8 et plus

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        while chunk := f.read(chunk_num_blocks*h.block_size): 
            h.update(chunk)
    return h.digest()

poste original

Si vous souhaitez une méthode plus pythique (pas de 'while True') pour lire le fichier, consultez ce code :

import hashlib

def checksum_md5(filename):
    md5 = hashlib.md5()
    with open(filename,'rb') as f: 
        for chunk in iter(lambda: f.read(8192), b''): 
            md5.update(chunk)
    return md5.digest()

Notez que le func iter() a besoin d'une chaîne d'octets vide pour que l'itérateur retourné s'arrête à EOF, puisque read() retourne b'' (et pas seulement '').

18 votes

Mieux encore, utilisez quelque chose comme 128*md5.block_size au lieu de 8192 .

1 votes

mrkj : Je pense qu'il est plus important de choisir la taille de votre bloc de lecture en fonction de votre disque et de s'assurer que c'est un multiple de md5.block_size .

6 votes

le site b'' la syntaxe était nouvelle pour moi. Expliqué ici .

52voto

Nathan Feger Points 7675

Voici ma version de la méthode de @Piotr Czapla :

def md5sum(filename):
    md5 = hashlib.md5()
    with open(filename, 'rb') as f:
        for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
            md5.update(chunk)
    return md5.hexdigest()

30voto

Sabbasth Points 314

En utilisant plusieurs commentaires/réponses dans ce fil, voici ma solution :

import hashlib
def md5_for_file(path, block_size=256*128, hr=False):
    '''
    Block size directly depends on the block size of your filesystem
    to avoid performances issues
    Here I have blocks of 4096 octets (Default NTFS)
    '''
    md5 = hashlib.md5()
    with open(path,'rb') as f: 
        for chunk in iter(lambda: f.read(block_size), b''): 
             md5.update(chunk)
    if hr:
        return md5.hexdigest()
    return md5.digest()
  • C'est "pythonique".
  • Il s'agit d'une fonction
  • Il évite les valeurs implicites : préférez toujours les valeurs explicites.
  • Il permet d'optimiser les performances (très importantes)

Et enfin,

- Ce site a été construit par une communauté, merci à tous pour vos conseils/idées.

3 votes

Une suggestion : faites de votre objet md5 un paramètre facultatif de la fonction pour permettre à d'autres fonctions de hachage, comme sha256, de remplacer facilement MD5. Je vais également proposer cette modification.

1 votes

aussi : digest n'est pas lisible par l'homme. hexdigest() permet une sortie plus compréhensible, communément reconnaissable ainsi qu'un échange plus facile du hash

0 votes

D'autres formats de hachage sont hors du champ de la question, mais la suggestion est pertinente pour une fonction plus générique. J'ai ajouté une option "lisible par l'homme" conformément à votre deuxième suggestion.

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