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


188

私はhashlib(Python 2.6 / 3.0のmd5を置き換えます)を使用しましたが、ファイルを開いてそのコンテンツをhashlib.md5()関数に配置すると問題なく動作しました。

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

ファイル全体をメモリに読み込まずにファイルのMD5ハッシュを取得する方法


20
「ファイル全体をメモリに読み込まずにMD5のファイルを取得するにはどうすればいいですか?」
XTL

回答:


147

ファイルを8192バイトのチャンク(または128バイトのその他の倍数)に分割し、を使用してそれらを連続的にMD5にフィードしますupdate()

これは、MD5に128バイトのダイジェストブロックがあることを利用しています(8192は128×64)。ファイル全体をメモリに読み込むわけではないので、8192バイトを超えるメモリは使用しません。

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

81
128の倍数のブロックサイズ(たとえば8192、32768など)を効果的に使用でき、一度に128バイトを読み取るよりもはるかに高速です。
jmanning2k 2009

40
この重要なメモをjmanning2kに感謝します。184MBファイルでのテストには(128、8192、32768)を使用して(0m9.230s、0m2.547s、0m2.429s)かかります。値が大きいほど目立たない影響を与えるため、8192を使用します。
JustRegisterMe 2009

可能であれば、のhashlib.blake2b代わりに使用してくださいmd5。MD5とは異なり、BLAKE2は安全で、さらに高速です。
ボリス

2
@Boris、あなたは実際にはBLAKE2が安全であるとは言えません。言えることは、まだ壊れていないということです。
vy32

@ vy32あなたもそれが間違いなく壊れるだろうとは言えません。100年後には見られるでしょうが、MD5よりも確実に安全ではありません。
ボリス

220

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

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」ツールを使用して結果をクロスチェックしました。

jacksum -a md5 <filename>

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


29
注意すべき重要なことは、この関数に渡されるファイルは、バイナリモードで、つまり関数に渡しrbて開く必要があるというopenことです。
Frerich Raabe、2011

11
これは単純な追加ですが、hexdigest代わりにdigestを使用すると、ハッシュのほとんどの例のように「見える」16進数のハッシュが生成されます。
tchaymore 2011年

そうじゃないのif len(data) < block_size: break
エリックカプルン

2
エリック、いいえ、なぜでしょうか?目標は、ファイルの終わりまで、すべてのバイトをMD5に送ることです。部分的なブロックを取得しても、すべてのバイトがチェックサムに送られるべきではありません。

2
@ user2084795はopen 常に、位置をファイルの先頭に設定して、新しいファイルハンドルを開きます(追加のためにファイルを開く場合を除く)。
スティーブバーンズ2017

110

以下に、コメントからの提案を組み込みました。ありがとうございます!

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以降

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

元の投稿

もっと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 ''( ''だけではない)を返すため、iter()funcは、返されたイテレーターがEOFで停止するために空のバイト文字列を必要とすることに注意してください。


17
さらに、の128*md5.block_size代わりになどを使用してください8192
mrkj 2011年

1
mrkj:ディスクに基づいて読み取りブロックサイズを選択し、それがの倍数であることを確認することがより重要だと思いますmd5.block_size
Harvey、

6
b''構文は、私には新鮮でした。ここで説明。
cod3monk3y 2014

1
@ThorSummoner:実際はそうではありませんが、フラッシュメモリに最適なブロックサイズを見つける私の作業から、32kのような数、または4、8、または16kで簡単に割り切れるものを選択することをお勧めします。たとえば、ブロックサイズが8kの場合、32kを読み取ると、正しいブロックサイズで4回の読み取りになります。16の場合は2ですが、どちらの場合も、偶数の整数のブロックを読み取っているので、問題ありません。
Harvey

1
「while True」はかなりpythonicです。
ユルゲン・A.エアハルト

49

これが@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()

30

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

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

そして最後に、

-これはコミュニティによって作成されました。アドバイス/アイデアをありがとうございます。


3
1つの提案:md5オブジェクトを関数のオプションのパラメーターにして、sha256などの代替ハッシュ関数でMD5を簡単に置き換えることができるようにします。これも編集として提案します。
Hawkwing 2013

1
また、ダイジェストは人間が読める形式ではありません。hexdigest()は、より理解しやすく、一般的に認識可能な出力と、ハッシュのより簡単な交換を可能にします
Hawkwing

他のハッシュ形式は問題の範囲外ですが、提案はより一般的な関数に関連しています。2番目の提案に従って、「人間が読める」オプションを追加しました。
Bastien Semene 2013

ここで「hr」がどのように機能しているのか詳しく説明できますか?
EnemyBagJones 2018年

@EnemyBagJones 'hr'は人間が読める形式です。32文字の長さの16進数の文字列を返します:docs.python.org/2/library/md5.html#md5.md5.hexdigest
Bastien Semene

8

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()センチネル(空の文字列)で関数を使用することです。

この場合に作成されたイテレータは、そのメソッドの呼び出しごとに引数なしでo [ラムダ関数]を呼び出しnext()ます。返される値がセンチネルと等しい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

3

一般的なハッシュ関数についての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

0

あなたは完全なコンテンツを読まないとそれをmd5にすることはできません。しかし、更新機能を使用して、ファイルのコンテンツをブロックごとに読み取ることができます。
m.update(a); m.update(b)はm.update(a + b)と同等です


0

次のコードはもっと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()

-1

Djangoの承認済み回答の実装:

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

ループは好きではありません。@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()

単純で明確なループを複数のラムダを含むfunctools.reduceアブレーションに置き換える理由は何ですか?プログラミングに慣例があるかどうかわからないが、これは壊れていない。
Naltharial

私の主な問題は、hashlibs APIが他のPythonとうまく連携しないことでした。たとえば、shutil.copyfileobjどれが密接に機能しないかを考えてみましょう。私の次のアイデアはfold(別名reduce)で、イテラブルを1つのオブジェクトにまとめます。例えばハッシュのように。hashlibこれを少し面倒にする演算子を提供していません。それにもかかわらず、ここではイテラブルを折りたたんでいました。
セバスチャンワグナー

-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
答えのコードをフォーマットし、答えを出す前にこのセクションを読んでください:stackoverflow.com/help/how-to-answer
Farside

1
テキストモードでファイルを1行ずつ読み取り、それをいじって、エンコードされた各行のmd5を出力しているため、これは正しく動作しません!
スティーブバーンズ2017

-4

この辺りで騒ぎが少なすぎないかわかりません。最近、md5とMySQLのblobとして保存されたファイルに問題が発生したため、さまざまなファイルサイズと簡単なPythonのアプローチを試しました。

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

2Kbから20Mbのファイルサイズの範囲では、顕著なパフォーマンスの違いを検出できなかったため、ハッシュを「チャンク」する必要はありませんでした。とにかく、Linuxがディスクにアクセスする必要がある場合、少なくとも平均的なプログラマーがディスクを使用しないようにする能力と同様に、ディスクを使用する必要があります。偶然にも、問題はmd5とは何の関係もありませんでした。MySQLを使用している場合は、md5()関数とsha1()関数がすでにそこにあることを忘れないでください。


2
これは質問への回答ではなく、20 MBはほとんど考慮されていません ここで説明するようにRAMに収まらない非常に大きなファイルいません。
Chris
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.