Получить MD5 хеш больших файлов в Python - PullRequest
176 голосов
/ 15 июля 2009

Я использовал hashlib (который заменяет md5 в Python 2.6 / 3.0), и он работал нормально, если я открыл файл и поместил его содержимое в функцию hashlib.md5().

Проблема связана с очень большими файлами, размер которых может превышать размер ОЗУ.

Как получить MD5-хеш файла без загрузки всего файла в память?

Ответы [ 13 ]

214 голосов
/ 15 июля 2009

Вам необходимо прочитать файл кусками подходящего размера:

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

ПРИМЕЧАНИЕ. Убедитесь, что вы открываете файл с помощью 'rb', иначе вы получите неправильный результат.

Так что, чтобы сделать все много одним способом - используйте что-то вроде:

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

Обновление, приведенное выше, основано на комментариях, предоставленных Фрерихом Раабе - и я проверил это и обнаружил, что это правильно на моей установке Windows Python 2.7.2

Я перепроверил результаты, используя инструмент 'jacksum'.

jacksum -a md5 <filename>

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

138 голосов
/ 15 июля 2009

Разбейте файл на 128-байтовые порции и последовательно подайте их в MD5, используя update().

Это использует тот факт, что MD5 имеет 128-байтовые блоки дайджеста. По сути, когда файл MD5 digest() s, это именно то, что он делает.

Если вы убедитесь, что освобождаете память на каждой итерации (т.е. не читаете весь файл в память), это займет не более 128 байт памяти.

Одним из примеров является чтение фрагментов следующим образом:

f = open(fileName)
while not endOfFile:
    f.read(128)
101 голосов
/ 18 ноября 2010

, если вам нужен более питонический (без 'True') способ чтения файла, проверьте этот код:

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

Обратите внимание, что для функции iter () требуется пустая строка байтов, чтобы возвращаемый итератор остановился на EOF, поскольку read () возвращает b '' (не просто '').

48 голосов
/ 21 июня 2012

Вот моя версия метода @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()
30 голосов
/ 22 июля 2013

Используя несколько комментариев / ответов в этой теме, вот мое решение:

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()
  • Это "питон"
  • Это функция
  • Избегает неявных значений: всегда предпочитайте явные.
  • Это позволяет (очень важно) оптимизировать производительность

И, наконец,

- Это было создано сообществом, спасибо всем за ваши советы / идеи.

7 голосов
/ 04 декабря 2016

Портативное решение Python 2/3

Чтобы вычислить контрольную сумму (md5, sha1 и т. Д.), Вы должны открыть файл в двоичном режиме, поскольку вы будете суммировать байтовые значения:

Чтобы быть переносимым с py27 / py3, вы должны использовать пакеты io, например:

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

Если ваши файлы большие, вы можете предпочесть чтение файла по частям, чтобы избежать хранения всего содержимого файла в памяти:

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

Хитрость здесь в том, чтобы использовать функцию iter() с sentinel (пустая строка).

Итератор, созданный в этом случае, будет вызывать o [лямбда-функцию] без аргументов для каждого вызова ее next() метода; если возвращаемое значение равно часовому, StopIteration будет увеличено, в противном случае будет возвращено значение.

Если ваши файлы действительно большие, вам также может понадобиться отобразить информацию о прогрессе. Вы можете сделать это, вызвав функцию обратного вызова, которая печатает или записывает количество вычисленных байтов:

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
4 голосов
/ 07 июля 2015

Ремикс кода Бастьена Семена, который учитывает комментарий Hawkwing об общей функции хеширования ...

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
1 голос
/ 15 июля 2009

вы не можете получить его md5 без чтения полного содержимого. но вы можете использовать функцию update , чтобы читать содержимое файла блок за блоком.
m.update (а); m.update (b) эквивалентно m.update (a + b)

0 голосов
/ 08 мая 2019

Я не люблю петли. По материалам @Nathan Feger:

md5 = hashlib.md5()
with open(filename, 'rb') as f:
    functools.reduce(lambda _, c: md5.update(c), iter(lambda: f.read(md5.block_size * 128), b''), None)
md5.hexdigest()
0 голосов
/ 28 апреля 2019

Я думаю, что следующий код более питоничен:

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