web-dev-qa-db-fra.com

Obtenir le hash MD5 des gros fichiers dans Python

J'ai utilisé hashlib (qui remplace md5 dans Python 2.6/3.0) et tout s'est bien passé si j'ai ouvert un fichier et mis son contenu dans hashlib.md5() = fonction.

Le problème vient de très gros fichiers que leur taille pourrait dépasser RAM taille.

Comment obtenir le hachage MD5 d'un fichier sans charger l'intégralité du fichier en mémoire?

180
JustRegisterMe

Divisez le fichier en morceaux de 128 octets et transmettez-les à MD5 consécutivement à l'aide de update().

Cela tire parti du fait que MD5 dispose de blocs de digestion de 128 octets. Fondamentalement, lorsque MD5 digest() reprend le fichier, c'est exactement ce qu'il fait.

Si vous vous assurez de libérer de la mémoire à chaque itération (c’est-à-dire de ne pas lire le fichier en entier dans la mémoire), la mémoire ne doit pas dépasser 128 octets.

Un exemple est de lire les morceaux comme suit:

f = open(fileName)
while not endOfFile:
    f.read(128)
139
Yuval Adam

Vous devez lire le fichier en 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 d’ouvrir votre fichier avec le 'rb' à l’ouverture - sinon vous obtiendrez un résultat erroné.

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 était basée sur les commentaires fournis par Frerich Raabe - et je l'ai testé et trouvé correct sur mon Python 2.7.2 installation de Windows

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

jacksum -a md5 <filename>

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

216
user25148

si vous vous souciez de plus de Pythonic (no 'While True') façon de lire le fichier, vérifiez 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 la fonction iter () a besoin d’une chaîne d’octets vide pour que l’itérateur renvoyé s’arrête à EOF, puisque read () renvoie b '' (pas seulement '').

101
Piotr Czapla

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()
48
Nathan Feger

En utilisant plusieurs commentaires/réponses dans ce fil de discussion, 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 "Pythonic"
  • C'est une fonction
  • Cela évite les valeurs implicites: préférez toujours les valeurs explicites.
  • Il permet des optimisations de performances (très importantes)

Et enfin,

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

30
Bastien Semene

A Python 2/3 solution portable

Pour calculer une somme de contrôle (md5, sha1, etc.), vous devez ouvrir le fichier en mode binaire, car vous allez additionner les valeurs d'octets:

Pour être py27/py3 portable, vous devriez utiliser les paquets io, comme ceci:

import hashlib
import io


def md5sum(src):
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        content = fd.read()
        md5.update(content)
    return md5

Si vos fichiers sont volumineux, vous préférerez peut-être lire le fichier par morceaux afin d'éviter de stocker tout le contenu du fichier en mémoire:

def md5sum(src, length=io.DEFAULT_BUFFER_SIZE):
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        for chunk in iter(lambda: fd.read(length), b''):
            md5.update(chunk)
    return md5

Le truc ici consiste à utiliser la fonction iter() avec un sentinel (la chaîne vide).

L'itérateur créé dans ce cas appellera o [la fonction lambda] sans argument pour chaque appel de sa méthode next(); si la valeur renvoyée est égale à sentinel, StopIteration sera levé, sinon la valeur sera renvoyée.

Si vos fichiers sont très gros , vous devrez peut-être également afficher des informations sur la progression. Vous pouvez le faire en appelant une fonction de rappel qui affiche ou enregistre la quantité d'octets calculés:

def md5sum(src, callback, length=io.DEFAULT_BUFFER_SIZE):
    calculated = 0
    md5 = hashlib.md5()
    with io.open(src, mode="rb") as fd:
        for chunk in iter(lambda: fd.read(length), b''):
            md5.update(chunk)
            calculated += len(chunk)
            callback(calculated)
    return md5
8
Laurent LAPORTE

n remix de code de Bastien Semene qui prend en compte le commentaire de Hawkwing sur la fonction de hachage générique ...

def hash_for_file(path, algorithm=hashlib.algorithms[0], block_size=256*128, human_readable=True):
    """
    Block size directly depends on the block size of your filesystem
    to avoid performances issues
    Here I have blocks of 4096 octets (Default NTFS)

    Linux Ext4 block size
    Sudo tune2fs -l /dev/sda5 | grep -i 'block size'
    > Block size:               4096

    Input:
        path: a path
        algorithm: an algorithm in hashlib.algorithms
                   ATM: ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
        block_size: a multiple of 128 corresponding to the block size of your filesystem
        human_readable: switch between digest() or hexdigest() output, default hexdigest()
    Output:
        hash
    """
    if algorithm not in hashlib.algorithms:
        raise NameError('The algorithm "{algorithm}" you specified is '
                        'not a member of "hashlib.algorithms"'.format(algorithm=algorithm))

    hash_algo = hashlib.new(algorithm)  # According to hashlib documentation using new()
                                        # will be slower then calling using named
                                        # constructors, ex.: hashlib.md5()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(block_size), b''):
             hash_algo.update(chunk)
    if human_readable:
        file_hash = hash_algo.hexdigest()
    else:
        file_hash = hash_algo.digest()
    return file_hash
4
Richard

vous ne pouvez pas obtenir c'est md5 sans lire le contenu complet. mais vous pouvez utiliser la fonction pdate pour lire le contenu des fichiers bloc par bloc.
m.update (a); m.update (b) est équivalent à m.update (a + b)

1
sunqiang

Je pense que le code suivant est plus Pythonic:

from hashlib import md5

def get_md5(fname):
    m = md5()
    with open(fname, 'rb') as fp:
        for chunk in fp:
            m.update(chunk)
    return m.hexdigest()
0
Waket Zheng