Створення контрольної суми файлу MD5


348

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


3
Чому б просто не використовувати md5sum?
kennytm

99
Зберігаючи його в Python, це полегшує управління сумісністю між платформами.
Олександр

Якщо ви хочете отримати рішення з "панеллю прогресу * або подібним (для дуже великих файлів), розгляньте це рішення: stackoverflow.com/questions/1131220/…
Laurent LAPORTE

1
@kennytm Посилання, яке ви надали, говорить про це у другому параграфі: "Основний алгоритм MD5 вже не вважається захищеним" під час опису md5sum. Ось чому програмісти, які усвідомлюють безпеку, не повинні використовувати його, на мою думку.
Налагодження255,

1
@ Debug255 Хороший та дійсний пункт. Як md5sumі метод , описаний в цьому питанні SO слід уникати - це краще використовувати SHA-2 або SHA-3, якщо це можливо: en.wikipedia.org/wiki/Secure_Hash_Algorithms
Per Lundberg

Відповіді:


464

Ви можете використовувати hashlib.md5 ()

Зауважте, що іноді ви не зможете вмістити весь файл у пам'яті. У такому випадку вам доведеться послідовно читати шматки 4096 байт і подавати їх md5методу:

import hashlib
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(), тому вам не доведеться конвертувати назад.


297

Є спосіб, який досить неефективний у пам’яті .

один файл:

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 зламаний, і його не слід використовувати з будь-якою метою, оскільки аналіз вразливості може бути справді складним, а аналіз будь-якого можливого подальшого використання вашого коду може бути застосований для проблем безпеки неможливо. IMHO, його слід вилучити з бібліотеки, тому кожен, хто ним користується, змушений оновлюватись. Отже, ось що вам слід зробити замість цього:

[(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 біт.


66
Я використовую лише MD5 для підтвердження того, що файл не пошкоджений. Мене не так хвилює, що це порушиться.
Олександр

87
@TheLifelessOne: І незважаючи на @Omnifarious страхітливі попередження, це ідеально добре використовувати MD5.
президент Джеймс К. Полк

22
@GregS, @TheLifelessOne - Так, і наступне, що ти знаєш, хтось знаходить спосіб використати цей факт щодо вашої програми, щоб файл сприйняв як непошкоджений, коли це зовсім не той файл, якого ви очікуєте. Ні, я стою біля своїх страшних попереджень. Я думаю, що MD5 слід видалити або прийти з попередженням про депресію.
всезнайко

10
Я, мабуть, використовував би .hexdigest () замість .digest () - людині це легше читати - яка мета ОП.
zbstof

21
Я використав це рішення, але воно невірно дало один і той же хеш для двох різних pdf-файлів. Рішенням було відкрити файли, вказавши бінарний режим, тобто: [(fname, hashlib.md5 (open (fname, 'rb' ) .read ()). Hexdigest ()) для fname in fnamelst] Це більше пов'язано до відкритої функції, ніж md5, але я вважав, що це може бути корисно повідомити про неї, враховуючи вимогу щодо сумісності між платформами, зазначене вище (див. також: docs.python.org/2/tutorial/… ).
BlueCoder

34

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

EDIT: Вибачте за велику затримку, я не переглядав це протягом певного часу, але, щоб відповісти на питання @ EdRandall, я напишу реалізацію Adler32. Однак я не виконував орієнтирів для цього. Це в принципі так само, як це було б у CRC32: замість init, оновлення та дайджесту дзвінків, все - це 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-Ву потрібно , щоб зробити це 32-розрядний ціле число без знака, який гарантує , що він повертає те ж значення між версіями Python.


Не могли б ви додати пару рядків, порівнюючи SHA1, а також, можливо, zlib.adler32?
Ед Рендалл

1
Функція md5sum () припускає, що у вас є доступ до файлу. Якщо замінити "r + b" у відкритому () дзвінку на "rb", воно буде добре працювати.
Кевін Ліда

1
@EdRandall: adler32 дійсно не варто турбуватися, наприклад. leviathansecurity.com/blog/analysis-of-adler32
MikeW

6

У 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.


:=Оператор є «оператор присвоювання» (новий Python 3.8+); це дозволяє призначити значення всередині більшого виразу; Докладніше тут: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Бенджамін

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

3
Привіт! Будь-ласка, додайте до свого коду пояснення, чому це рішення проблеми. Крім того, ця публікація досить стара, тому вам слід також додати інформацію про те, чому ваше рішення додає те, чого інші ще не звернули увагу.
d_kennetz

1
Це ще один неефективний спосіб пам'яті
Ерік Аронесті

-2

Я думаю, що покладатися на виклик пакета та md5sum бінарний трохи зручніше, ніж підпроцес або пакет md5

import invoke

def get_file_hash(path):

    return invoke.Context().run("md5sum {}".format(path), hide=True).stdout.split(" ")[0]

Звичайно, це передбачає, що у вас встановлено виклик та md5sum.


3
Якщо pathце призначений користувачем шлях, це дозволить будь-якому користувачеві виконувати довільні команди bash у вашій системі.
Борис
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.