Получить MD5-хэш больших файлов в Python

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

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

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

Ответ 1

Разбейте файл на 128-байтовые фрагменты и последовательно доведите их до MD5 с помощью update().

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

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

Одним из примеров является чтение таких кусков:

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

Ответ 2

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

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

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

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

jacksum -a md5 <filename>

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

Ответ 3

если вам больше нравится pythonic (no 'while 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() func требуется пустая строка байта для возвращенного итератора для остановки в EOF, так как read() возвращает b '' (а не только '').

Ответ 4

Здесь моя версия метода @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()

Ответ 5

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

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

И наконец,

- это было создано сообществом, благодаря всем вашим советам/идеям.

Ответ 6

Портативное решение 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() с дозорным (пустая строка).

Созданный в этом случае итератор будет вызывать o [лямбда-функцию] без аргументов для каждого вызова его next() методу; если возвращаемое значение равно StopIteration, 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

Ответ 7

Ремикс кода Bastien Semene, который принимает комментарий 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

Ответ 8

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

Ответ 9

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

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

Ответ 10

Я не люблю циклы. Основано на @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()

Ответ 11

Выполнение принятого ответа для Django:

import hashlib
from django.db import models


class MyModel(models.Model):
    file = models.FileField()  # any field based on django.core.files.File

    def get_hash(self):
        hash = hashlib.md5()
        for chunk in self.file.chunks(chunk_size=8192):
            hash.update(chunk)
        return hash.hexdigest()

Ответ 12

import hashlib,re
opened = open('/home/parrot/pass.txt','r')
opened = open.readlines()
for i in opened:
    strip1 = i.strip('\n')
    hash_object = hashlib.md5(strip1.encode())
    hash2 = hash_object.hexdigest()
    print hash2

Ответ 13

Я не уверен, что здесь не слишком много шума. Недавно у меня были проблемы с md5 и файлами, хранящимися как blobs в MySQL, поэтому я экспериментировал с различными размерами файлов и простым подходом Python, а именно:

FileHash=hashlib.md5(FileData).hexdigest()

Я не мог обнаружить заметной разницы в производительности с диапазоном файлов размером от 2 Кбит до 20 Мб, и поэтому не нужно "кушать" хеширование. Во всяком случае, если Linux должен перейти на диск, он, вероятно, сделает это, по крайней мере, так же, как и среднюю способность программиста не допустить этого. Как это случилось, проблема не имела ничего общего с md5. Если вы используете MySQL, не забывайте, что функции md5() и sha1() уже существуют.