Генерация контрольной суммы MD5 файла - PullRequest
278 голосов
/ 07 августа 2010

Есть ли простой способ генерирования (и проверки) контрольных сумм MD5 списка файлов в Python?(У меня есть небольшая программа, над которой я работаю, и я хотел бы подтвердить контрольные суммы файлов).

Ответы [ 4 ]

372 голосов
/ 07 августа 2010

Вы можете использовать hashlib.md5 ()

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

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

Примечание: hash_md5.hexdigest() вернет гекс строка представление для дайджеста, если вам просто нужны упакованные байты, используйте return hash_md5.digest(), поэтому вам не нужно конвертировать обратно.

285 голосов
/ 07 августа 2010

Есть способ, которым довольно памяти неэффективно .

один файл:

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

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

список файлов:

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

Напомним, однако, что MD5 известен как неисправный и не должен использоваться для каких-либо целей, так как анализ уязвимостей может быть очень сложным, а анализ любого возможного будущего использования вашего кода может быть применен для решения проблем безопасности, невозможен , ИМХО, он должен быть полностью удален из библиотеки, чтобы все, кто его использует, были вынуждены обновляться. Итак, вот что вы должны сделать вместо этого:

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

Если вам нужен только 128-разрядный дайджест, вы можете сделать .digest()[:16].

Это даст вам список кортежей, каждый из которых содержит имя своего файла и его хеш.

Опять я сильно сомневаюсь, что вы используете MD5. Вы должны по крайней мере использовать SHA1, и учитывая недавние недостатки, обнаруженные в SHA1 , вероятно, даже не это. Некоторые люди думают, что пока вы не используете MD5 для «криптографических» целей, все в порядке. Но вещи в конечном итоге оказываются шире, чем вы изначально ожидали, и ваш случайный анализ уязвимостей может оказаться совершенно ошибочным. Лучше всего просто привыкнуть использовать правильный алгоритм из ворот. Это просто набор другой связки букв и все. Это не так сложно.

Вот способ более сложный, но эффективный для памяти :

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]

И, опять же, поскольку MD5 сломан и больше не должен использоваться:

[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
    for fname in fnamelst]

Опять же, вы можете поставить [:16] после вызова hash_bytestr_iter(...), если вы хотите получить только 128-битный дайджест.

31 голосов
/ 05 февраля 2014

Я явно не добавляю ничего принципиально нового, но добавил этот ответ, прежде чем приступил к комментированию статуса, плюс регионы кода проясняют ситуацию - во всяком случае, специально для ответа на вопрос @ Nemo из ответа Omnifarious:

Я немного подумал о контрольных суммах (пришел сюда в поисках предложений по размерам блоков, в частности) и обнаружил, что этот метод может быть быстрее, чем вы ожидаете.Взяв самый быстрый (но довольно типичный) результат timeit.timeit или /usr/bin/time от каждого из нескольких методов проверки контрольной суммы файла ок.11 МБ:

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

Итак, похоже, что для Python и / usr / bin / md5sum требуется около 30 мс для файла размером 11 МБ.Соответствующая функция md5sum (md5sum_read в вышеприведенном листинге) очень похожа на функцию 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()

Конечно, они из одиночных прогонов (mmap всегда на несколько быстрее, когда впо крайней мере, несколько десятков запусков), и у моего обычно получается дополнительный f.read(blocksize) после того, как буфер исчерпан, но он достаточно повторяем и показывает, что md5sum в командной строке не обязательно быстрее, чем реализация Python ...

РЕДАКТИРОВАТЬ: Извините за долгую задержку, некоторое время не смотрел на это, но чтобы ответить на вопрос @ EdRandall, я запишу реализацию Adler32.Тем не менее, я не проводил тесты для этого.По сути, это то же самое, что и CRC32: вместо вызовов init, update и digest все это zlib.adler32() call:

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

Обратите внимание, что это должно начинаться с пустой строки,поскольку суммы Адлера действительно различаются, когда начинаются с нуля, по сравнению с их суммой для "", то есть 1 - CRC может начинаться с 0.AND -ing необходим для того, чтобы сделать его 32-разрядным целым числом без знака, что гарантирует, что оно возвращает одно и то же значение во всех версиях Python.

0 голосов
/ 24 апреля 2019
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()
...