Python'daki büyük dosyaların MD5 karmasını alın


188

Ben hashlib (Python 2.6 / 3.0 md5 yerine) kullandım ve bir dosya açtı ve içeriğini hashlib.md5()işlev koymak eğer iyi çalıştı .

Sorun, boyutlarının RAM boyutunu aşabileceği çok büyük dosyalarda.

Tüm dosyayı belleğe yüklemeden bir dosyanın MD5 karmasını nasıl alabilirim?


20
Yeniden ifade ederim: "MD5, tüm dosyayı belleğe yüklemeden bir dosyayı nasıl alabilirim?"
XTL

Yanıtlar:


147

Dosyayı 8192 baytlık parçalara (veya 128 baytlık başka bir katlara) ayırın ve ard arda kullanarak bunları MD5'e besleyin update().

Bu, MD5'in 128 baytlık sindirim bloklarına sahip olmasından yararlanır (8192, 128 × 64'tür). Tüm dosyayı belleğe okumadığınız için, bu 8192 bayttan fazla bellek kullanmayacaktır.

Python 3.8+ ile şunları yapabilirsiniz

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

81
128'den (8192, 32768 vb.) Herhangi bir katının blok boyutunu etkili bir şekilde kullanabilirsiniz ve bu bir seferde 128 bayt okumaktan çok daha hızlı olacaktır.
jmanning2k

40
Bu önemli not için teşekkürler jmanning2k, 184MB dosyasındaki bir test (0m9.230s, 0m2.547s, 0m2.429s) 'i (128, 8192, 32768) kullanarak alır, daha yüksek değer fark edilmeyen etki verirken 8192'yi kullanacağım.
JustRegisterMe

Yapabiliyorsanız, hashlib.blake2byerine kullanmalısınız md5. MD5'in aksine, BLAKE2 güvenlidir ve daha da hızlıdır.
Boris

2
@Boris, aslında BLAKE2'nin güvenli olduğunu söyleyemezsiniz. Söyleyebileceğin tek şey henüz kırılmadı.
vy32

@ vy32 kesinlikle kırılacağını söyleyemezsin. 100 yıl içinde göreceğiz, ama en azından kesinlikle güvensiz olan MD5'ten daha iyi.
Boris

220

Dosyayı uygun boyutta parçalar halinde okumalısınız:

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

NOT: Dosyanızı 'rb' açık olarak açtığınızdan emin olun - aksi takdirde yanlış sonuç alırsınız.

Bu nedenle, tüm lotu tek bir yöntemle yapmak için - şöyle bir şey kullanın:

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

Yukarıdaki güncelleme Frerich Raabe tarafından sağlanan yorumlara dayanıyordu - ve bunu test ettim ve Python 2.7.2 Windows kurulumumda doğru olduğunu buldum

'Jacksum' aracını kullanarak sonuçları çapraz kontrol ettim.

jacksum -a md5 <filename>

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


29
Ne haber önemlidir bu işleve geçirilen dosya geçirerek ikili modda, yani açılmalıdır olmasıdır rbiçin openişlevin.
Frerich Raabe

11
Bu basit bir ektir, ancak hexdigestbunun yerine kullanmak, digestçoğu karma örneğine benzeyen onaltılık bir karma üretecektir.
tchaymore

Olmamalı mı if len(data) < block_size: break?
Erik Kaplun

2
Erik, hayır, neden olsun ki? Amaç, dosyanın sonuna kadar tüm baytları MD5'e beslemektir. Kısmi bir blok almak, tüm baytların sağlama toplamına beslenmemesi gerektiği anlamına gelmez.

2
@ user2084795 open her zaman konumun dosyanın başlangıcına ayarlanmış olarak yeni bir dosya tanıtıcısı açar (bir dosyayı eklemek için açmazsanız).
Steve Barnes

110

Aşağıda yorumlardan öneri ekledim. Teşekkürler al!

python <3.7

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''): 
            h.update(chunk)
    return h.digest()

python 3.8 ve üstü

import hashlib

def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
    h = hash_factory()
    with open(filename,'rb') as f: 
        while chunk := f.read(chunk_num_blocks*h.block_size): 
            h.update(chunk)
    return h.digest()

orijinal gönderi

dosyayı okumak için daha fazla pythonic (no 'while True') yöntemiyle ilgileniyorsanız bu kodu kontrol edin:

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

İter () işlevinin döndürülen yineleyicinin EOF'da durması için boş bir bayt dizesine ihtiyacı olduğunu unutmayın, çünkü read () b '' değerini döndürür (yalnızca '').


17
Daha da iyisi, 128*md5.block_sizebunun yerine bir şey kullanın 8192.
mrkj

1
mrkj: Bence diskinize göre okuma bloğu boyutunu seçmek ve daha sonra bunun birden fazla olmasını sağlamak daha önemlidir md5.block_size.
Harvey

6
b''sözdizimi benim için yeni oldu. Açıklaması burada .
cod3monk3y

1
@ThorSummoner: Gerçekten değil, ancak flash bellek için en uygun blok boyutlarını bulmaya çalışırken, sadece 32k gibi bir sayı veya 4, 8 veya 16k ile kolayca bölünebilen bir şey seçmenizi öneririm. Örneğin, blok boyutunuz 8k ise, 32k değeri doğru blok boyutunda 4 okuma olacaktır. 16, o zaman 2 ise. Ama her durumda, biz iyiyiz çünkü bir tam sayı çoklu blokları okuyoruz.
Harvey

1
"True ise" oldukça pitoniktir.
Jürgen A. Erhard

49

İşte @Piotr Czapla yönteminin sürümü:

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

30

Bu konuda birden fazla yorum / cevap kullanarak, benim çözüm burada:

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()
  • Bu "pitonik"
  • Bu bir işlev
  • Örtük değerleri önler: her zaman müstehcen değerleri tercih edin.
  • (Çok önemli) performans optimizasyonlarına izin verir

Ve sonunda,

- Bu bir topluluk tarafından inşa edilmiştir, tüm tavsiyeleriniz / fikirleriniz için teşekkürler.


3
Bir öneri: md5 nesnenizi, MD5'in kolayca değiştirilebilmesi için sha256 gibi alternatif sağlama işlevlerine izin vermek için işlevin isteğe bağlı bir parametresi yapın. Bunu bir düzenleme olarak da önereceğim.
Hawkwing

1
ayrıca: sindirim insan tarafından okunamaz. hexdigest (), daha anlaşılır, yaygın olarak yeniden kaydedilebilir bir çıktıya ve karma işleminin daha kolay değiştirilmesine izin verir
Hawkwing

Diğer karma biçimleri sorunun kapsamı dışındadır, ancak öneri daha genel bir işlevle ilgilidir. 2. önerinize göre "insan tarafından okunabilir" bir seçenek ekledim.
Bastien Semene

Burada 'hr' işlevinin nasıl çalıştığını açıklayabilir misiniz?
EnemyBagJones

@EnemyBagJones 'hr' insan tarafından okunabilir anlamına gelir. 32 karakter uzunluğunda onaltılık basamaktan oluşan bir dize döndürür: docs.python.org/2/library/md5.html#md5.md5.hexdigest
Bastien Semene 27:18

8

Python 2/3 taşınabilir bir çözüm

Bir sağlama toplamı (md5, sha1 vb.) Hesaplamak için dosyayı ikili modda açmalısınız, çünkü bayt değerlerini toplarsınız:

Py27 / py3 taşınabilir olmak için, iopaketleri aşağıdaki gibi kullanmalısınız :

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

Dosyalarınız büyükse, dosya içeriğinin tamamını bellekte saklamaktan kaçınmak için dosyayı yığınlarla okumayı tercih edebilirsiniz:

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

Buradaki hile, iter()işlevi bir sentinel (boş dize) ile kullanmaktır.

Bu durumda oluşturulan yineleyici , yönteme yapılan her çağrı için argüman olmadan o [lambda işlevi] 'ni çağırır next(); döndürülen değer sentinel değerine eşitse StopIterationyükseltilir, aksi takdirde değer döndürülür.

Dosyalarınız gerçekten büyükse, ilerleme durumu bilgilerini de görüntülemeniz gerekebilir. Bunu, hesaplanan bayt miktarını yazdıran veya kaydeden bir geri arama işlevini çağırarak yapabilirsiniz:

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

3

Hawkwing'in genel karma işlevi hakkında yorum yapan Bastien Semene kodunun bir remiksi ...

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

0

u tam içeriği okumak olmadan md5 alınamıyor. ancak u blok içeriğini dosya içeriğini okumak için güncelleme fonksiyonunu kullanabilirsiniz .
m.update (a) ' m. güncelleme (b) m. güncelleme (a + b) ile eşdeğerdir


0

Aşağıdaki kod daha pitonik olduğunu düşünüyorum:

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

-1

Django için kabul edilen cevabın uygulanması:

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

-1

Döngülerden hoşlanmıyorum. @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()

Basit ve net bir döngüyü bir functools ile değiştirmek için olası nedenler var. Birden fazla lambda içeren abberasyonu azaltın? Programlama konusunda herhangi bir kural olup olmadığından emin değilim.
Naltharial

Benim asıl sorun hashlibs API gerçekten Python geri kalanı ile iyi oynamadı oldu. Örneğin shutil.copyfileobjhangisinin işe yaramadığını ele alalım . Bir sonraki fikrim fold(aka reduce) yinelemeleri tek bir nesneye katlayan. Bir hash gibi. hashlibbunu biraz hantal yapan operatörler sağlamaz. Yine de burada yinelenebilir bir katlama vardı.
Sebastian Wagner

-3
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

1
lütfen yanıttaki kodu biçimlendirin ve yanıt vermeden önce bu bölümü okuyun: stackoverflow.com/help/how-to-answer
Farside

1
Dosyayı metin modunda satır satır okuduktan sonra onunla uğraştığından ve soyulmuş, kodlanmış her satırın md5'ini yazdırdığı için bu düzgün çalışmaz!
Steve Barnes

-4

Buralarda biraz fazla telaşlanma olmadığından emin değilim. Son zamanlarda md5 ve MySQL üzerinde blob olarak saklanan dosyaları ile ilgili sorunlar vardı, bu yüzden çeşitli dosya boyutları ve basit Python yaklaşımı, viz ile denedim:

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

Ben 2Kb ila 20Mb dosya boyutları bir dizi fark edilir bir performans farkı tespit ve bu nedenle karma 'yığın' gerek yok. Her neyse, Linux'un diske gitmesi gerekiyorsa, muhtemelen en azından ortalama bir programcının bunu yapmasını engelleme yeteneğini de yapacak. Olduğu gibi, sorun md5 ile ilgisi yoktu. MySQL kullanıyorsanız, orada zaten md5 () ve sha1 () işlevlerini unutmayın.


2
Bu soruya cevap vermiyor ve 20 MB, burada tartışıldığı gibi RAM'e sığmayabilecek çok büyük bir dosya olarak kabul edilmiyor.
Chris
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.