Создание контрольной суммы MD5 файла

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

Ответ 1

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

Ответ 2

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

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

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] дайджест, вы можете сделать .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- hash_bytestr_iter(...) дайджест.

Ответ 3

Я явно не добавляю ничего принципиально нового, но добавил этот ответ, прежде чем я стал комментировать статус, плюс регионы кода сделали вещи более ясными - в любом случае, в частности, чтобы ответить на вопрос @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...

EDIT: Извините за долгую задержку, не посмотрел на это через какое-то время, но, чтобы ответить на вопрос @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

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

Ответ 4

import os
import hashlib

if os.path.exists(file_path) == False:
    return None

md5 = hashlib.md5()

f = open(file_path)
for line in f:
    md5.update(line)

f.close()

return md5.hexdigest()