簡體   English   中英

在 Python 中獲取大文件的 MD5 哈希值

[英]Get MD5 hash of big files in Python

我使用了 hashlib(它替換了 Python 2.6/3.0 中的 md5),如果我打開一個文件並將其內容放在hashlib.md5()函數中, hashlib.md5()正常工作。

問題在於非常大的文件,它們的大小可能超過 RAM 大小。

如何在不將整個文件加載到內存的情況下獲取文件的 MD5 哈希?

您需要以合適大小的塊讀取文件:

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”打開文件 - 否則您將得到錯誤的結果。

所以要用一種方法做很多事情 - 使用類似的東西:

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/

將文件分成 8192 字節的塊(或其他一些 128 字節的倍數),並使用update()將它們連續提供給 MD5。

這利用了 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

下面我結合了評論中的建議。 謝謝你!

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

請注意, iter() 函數需要一個空字節字符串,以便返回的迭代器在 EOF 處停止,因為 read() 返回 b''(不僅僅是 '')。

這是我的@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()

在此線程中使用多個評論/答案,這是我的解決方案:

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”
  • 這是一個函數
  • 它避免了隱式值:總是喜歡顯式值。
  • 它允許(非常重要的)性能優化

最后,

- 這是由社區建立的,感謝大家的建議/想法。

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()函數。

在這種情況下創建的迭代器將在每次調用next()方法時不帶參數地調用o [lambda 函數]; 如果返回的值等於 sentinel,則將引發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

Bastien Semene 代碼的混音,考慮了 Hawkwing 關於通用哈希函數的評論......

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

如果不閱讀完整內容,您將無法獲得它的 md5。 但是您可以使用更新功能逐塊讀取文件內容。
m.更新(一); m.update(b) 等價於 m.update(a+b)

我認為以下代碼更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()

我不喜歡循環。 基於@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()

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

我不確定這里是否有太多的大驚小怪。 我最近在使用 md5 和在 MySQL 上存儲為 blob 的文件時遇到了問題,因此我嘗試了各種文件大小和簡單的 Python 方法,即:

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

在 2Kb 到 20Mb 的文件大小范圍內,我沒有發現明顯的性能差異,因此不需要“分塊”散列。 無論如何,如果 Linux 必須使用磁盤,它可能至少會像普通程序員阻止它這樣做的能力一樣做到這一點。 碰巧,問題與md5無關。 如果您正在使用 MySQL,請不要忘記已經存在的 md5() 和 sha1() 函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM