web-dev-qa-db-ja.com

Pythonの大きなファイルのMD5ハッシュを取得します

Hashlib(Python 2.6/3.0のmd5を置き換える)を使用しましたが、ファイルを開いてその内容を hashlib.md5() 関数に入れると正常に機能しました。

問題は、サイズがRAMサイズを超える可能性がある非常に大きなファイルの場合です。

ファイル全体をメモリにロードせずにファイルのMD5ハッシュを取得する方法は?

180
JustRegisterMe

ファイルを128バイトのチャンクに分割し、update()を使用して連続してMD5にフィードします。

これは、MD5に128バイトのダイジェストブロックがあるという事実を利用しています。基本的に、MD5がdigest()sファイルの場合、これはまさにそれが行っていることです。

各反復でメモリを解放する(つまり、ファイル全体をメモリに読み取らない)ことを確認する場合、これには128バイト以下のメモリが必要です。

1つの例は、次のようにチャンクを読み取ることです。

f = open(fileName)
while not endOfFile:
    f.read(128)
139
Yuval Adam

適切なサイズのチャンクでファイルを読み取る必要があります。

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

注:必ず「rb」を指定してファイルを開いてください。そうしないと、間違った結果が得られます。

したがって、1つの方法ですべてを行うには、次のようなものを使用します。

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

上記の更新はFrerich Raabeによって提供されたコメントに基づいていました-これをテストし、Python 2.7.2 Windowsインストールで正しいことがわかりました

「ジャックサム」ツールを使用して結果をクロスチェックしました。

jacksum -a md5 <filename>

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

216
user25148

ファイルを読むためのよりPythonicな(「while True」ではない)方法が必要な場合は、次のコードを確認してください。

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

Read()が ''だけでなく)b ''を返すため、返された反復子がEOFで停止するためには、iter()funcが空のバイト文字列を必要とすることに注意してください。

101
Piotr Czapla

@Piotr Czaplaのメソッドの私のバージョンは次のとおりです。

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()
48
Nathan Feger

このスレッドで複数のコメント/回答を使用して、ここに私の解決策があります:

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()
  • これは「Pythonic」です
  • これは機能です
  • 暗黙的な値を回避します。常に明示的な値を優先します。
  • (非常に重要な)パフォーマンスの最適化が可能

そして最後に、

-これはコミュニティによって構築されました。アドバイス/アイデアに感謝します。

30
Bastien Semene

A Python 2/3ポータブルソリューション

チェックサム(md5、sha1など)を計算するには、バイト値を合計するため、ファイルをバイナリモードで開く必要があります。

Py27/py3ポータブルにするには、次のようにioパッケージを使用する必要があります。

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

ファイルが大きい場合は、ファイルの内容全体をメモリに保存しないように、ファイルをチャンクで読み取ることをお勧めします。

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

ここでのコツは、 iter() 関数をsentinel(空の文字列)とともに使用することです。

この場合に作成される反復子は、next()メソッドの呼び出しごとに引数なしでo[ラムダ関数]を呼び出します。返される値がセンチネルに等しい場合、StopIterationが発生します。それ以外の場合、値が返されます。

ファイルが本当に大きい場合は、進捗情報も表示する必要があります。これを行うには、計算されたバイト数を出力または記録するコールバック関数を呼び出します。

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
8
Laurent LAPORTE

一般的なハッシュ関数に関するHawkwingのコメントを考慮に入れたBastien Semeneコードのリミックス...

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
4
Richard

完全なコンテンツを読まないとmd5を取得できません。しかし、uは pdate 関数を使用して、ブロックごとにファイルコンテンツを読み取ることができます。
m.update(a); m.update(b)はm.update(a + b)と同等です

1
sunqiang

次のコードはもっとPythonicだと思います:

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()
0
Waket Zheng