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

40
이 중요한 메모에 대해 jmanning2k에게 감사드립니다 .184MB 파일에 대한 테스트는 (128, 8192, 32768)을 사용하여 (0m9.230s, 0m2.547s, 0m2.429s) 걸립니다. 높은 값은 눈에 띄지 않는 영향을 미치므로 8192를 사용합니다.
— JustRegisterMe

가능하면 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'를 사용하여 파일을 열어야합니다. 그렇지 않으면 잘못된 결과가 나타납니다.

따라서 한 가지 방법으로 모든 것을 수행하려면 다음과 같이 사용하십시오.

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

11
이것은 간단한 추가 사항이지만 hexdigest대신 대신 사용 digest하면 대부분의 해시 예와 같이 "보이는"16 진 해시가 생성됩니다.
— tchaymore

그렇지 if len(data) < block_size: break않습니까?
— Erik Kaplun

2
에릭, 아니, 왜 그래? 목표는 파일이 끝날 때까지 모든 바이트를 MD5에 공급하는 것입니다. 부분 블록을 얻는 것이 모든 바이트를 체크섬에 공급해서는 안된다는 의미는 아닙니다.

2
@ user2084795는 open 항상 파일의 시작에 위치 세트와 신선한 파일 핸들을 엽니 다 (당신이 APPEND에 대한 파일을 열 경우).
— Steve Barnes

110

아래에 의견의 제안을 통합했습니다. 알 감사합니다!

파이썬 <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()

파이썬 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 ( 'truele'없음) 방법에 관심이 있다면 다음 코드를 확인하십시오.

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

1
mrkj : 디스크를 기준으로 읽기 블록 크기를 선택한 다음이 값이 배수인지 확인하는 것이 더 중요하다고 생각합니다 md5.block_size.
— Harvey

6
b''구문은 나에게 새로운이었다. 여기에 설명 했다 .
— cod3monk3y

1
@ThorSummoner : 실제로는 아니지만 플래시 메모리에 대한 최적의 블록 크기를 찾는 작업에서 32k 또는 4, 8 또는 16k로 쉽게 나눌 수있는 숫자를 선택하는 것이 좋습니다. 예를 들어, 블록 크기가 8k이면 올바른 블록 크기에서 32k를 4 번 읽습니다. 16, 2이면 각각의 경우 정수 배수의 블록을 읽게되므로 좋습니다.
— Harvey

1
"진정한 동안"은 꽤 파이썬입니다.
— Jürgen A. Erhard

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()
  • 이것은 "파이 토닉"입니다
  • 이것은 기능입니다
  • 암시적인 값을 피합니다. 항상 명시적인 값을 선호합니다.
  • 그것은 (매우 중요한) 성능 최적화를 허용합니다

그리고 마지막으로,

-이것은 귀하의 조언 / 아이디어에 감사드립니다.


3
한 가지 제안 : md5 객체를 sha256과 같은 대체 해싱 함수가 MD5를 쉽게 대체 할 수 있도록 함수의 선택적 매개 변수로 만듭니다. 나는 이것을 편집으로 제안 할 것이다.
— Hawkwing 2009 년

1
또한 다이제스트는 사람이 읽을 수 없습니다. hexdigest ()는보다 이해하기 쉽고 일반적으로 조정 가능한 출력과 해시 교환을 용이하게합니다
— Hawkwing

다른 해시 형식은 질문의 범위를 벗어 났지만 제안은보다 일반적인 기능과 관련이 있습니다. 두 번째 제안에 따라 "사람이 읽을 수있는"옵션을 추가했습니다.
— Bastien Semene

여기서 'hr'이 어떻게 작동하는지 자세히 설명 할 수 있습니까?
— EnemyBagJones

@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

u 전체 내용을 읽지 않으면 md5를 얻을 수 없습니다. 그러나 u는 업데이트 기능을 사용 하여 파일 내용을 블록 단위로 읽을 수 있습니다 .
m. 업데이트 (a); m.update (b)는 m.update (a + b)와 같습니다.


0

다음 코드는 더 파이썬 적이라고 생각합니다.

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 abberation으로 대체하는 가능한 이유는 무엇입니까? 프로그래밍에 관한 규칙이 있는지 확실하지 않습니다.
— Naltharial

내 주요 문제는 hashlibs API가 나머지 Python과 제대로 작동하지 않는다는 것입니다. 예를 들어 shutil.copyfileobj작동하지 않는 것을 예로 들어 봅시다 . 내 다음 아이디어는 fold(일명 reduce) 반복 가능 항목을 단일 객체로 접는 것입니다. 예를 들어 해시처럼. hashlib이 작업을 약간 성가 시게하는 연산자를 제공하지 않습니다. 그럼에도 불구하고 여기에서 iterable을 접는 중이었습니다.
— Sebastian Wagner 19

-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
텍스트 모드에서 파일을 한 줄씩 읽은 다음 파일을 엉망으로 만들고 각 스트립, 인코딩 된 줄의 md5를 인쇄하므로 올바르게 작동하지 않습니다!
— Steve Barnes

-4

나는 여기에 너무 많은 소란이 없다는 것을 확신하지 못한다. 나는 최근에 md5와 MySQL에서 blob으로 저장된 파일에 문제가 있었기 때문에 다양한 파일 크기와 간단한 Python 접근법 인 viz를 실험했습니다.

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

2Kb ~ 20Mb의 파일 크기 범위에서 눈에 띄는 성능 차이를 감지 할 수 없으므로 해싱을 '청크'할 필요가 없습니다. 어쨌든, 리눅스가 디스크로 가야한다면, 아마 최소한의 프로그래머뿐만 아니라 리눅스가 그렇게하지 못하게 할 것입니다. 이 문제는 md5와 관련이 없습니다. MySQL을 사용하는 경우 이미 md5 () 및 sha1 () 함수를 잊지 마십시오.


2
이것은 질문에 대한 답이 아니며 20MB는 여기에 설명 된 것처럼 RAM에 맞지 않는 매우 큰 파일로 간주 되지 않습니다.
— Chris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.