Генерация контрольной суммы MD5 файла

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

9 ответов

Решение

Вы можете использовать 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()так что вам не нужно конвертировать обратно.

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

отдельный файл:

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 битов дайджеста.

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

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

$ ./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's:

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() вызов:

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.

В Python 3.8+ вы можете делать

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

Рассмотрите возможность использования hashlib.blake2b вместо того md5 (просто замените md5 с blake2bв приведенном выше фрагменте). Это криптографически безопасно и быстрее, чем MD5.

hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()

В Python 3.11+ появился новый удобочитаемый и эффективный для памяти метод :

      import hashlib
with open(path, "rb") as f:
    digest = hashlib.file_digest(f, "md5")
print(digest.hexdigest())

Вы могли бы использовать1 , который просто используетsubprocessзвонитьopensslдля macOS/Linux иCertUtilдля Windows и извлекает из вывода только дайджест:

Монтаж:

      pip install simple-file-checksum

Использование:

      >>> from simple_file_checksum import get_checksum
>>> get_checksum("path/to/file.txt")
'9e107d9d372bb6826bd81d3542a419d6'
>>> get_checksum("path/to/file.txt", algorithm="MD5")
'9e107d9d372bb6826bd81d3542a419d6'

The SHA1,SHA256,SHA384, иSHA512также поддерживаются алгоритмы.


1 Раскрытие информации: я авторsimple-file-checksum.

вы можете использовать оболочку здесь.

      from subprocess import check_output

#for windows & linux
hash = check_output(args='md5sum imp_file.txt', shell=True).decode().split(' ')[0]

#for mac
hash = check_output(args='md5 imp_file.txt', shell=True).decode().split('=')[1]

изменить file_path в ваш файл

      import hashlib
def getMd5(file_path):
    m = hashlib.md5()
    with open(file_path,'rb') as f:
        line = f.read()
        m.update(line)
    md5code = m.hexdigest()
    return md5code
Другие вопросы по тегам