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.