Gerando uma soma de verificação MD5 de um arquivo

348

Existe alguma maneira simples de gerar (e verificar) somas de verificação MD5 de uma lista de arquivos em Python? (Eu tenho um pequeno programa em que estou trabalhando e gostaria de confirmar as somas de verificação dos arquivos).

Alexander
fonte
3
Por que não usar apenas md5sum?
Kennytm
99
Mantê-lo em Python facilita o gerenciamento da compatibilidade entre plataformas.
Alexander
Se você deseja uma solução com "barra de progresso * ou semelhante (para arquivos muito grandes), considere esta solução: stackoverflow.com/questions/1131220/…
Laurent LAPORTE 4/16/16
11
@kennytm O link que você forneceu diz isso no segundo parágrafo: "O algoritmo MD5 subjacente não é mais considerado seguro" durante a descrição md5sum. É por isso que programadores preocupados com segurança não devem usá-lo na minha opinião.
Debug255
11
@ Debug255 Ponto bom e válido. Ambos md5sume a técnica descrita nesta questão SO deve ser evitado - é melhor usar SHA-2 ou SHA-3, se possível: en.wikipedia.org/wiki/Secure_Hash_Algorithms
Per Lundberg

Respostas:

462

Você pode usar hashlib.md5 ()

Observe que, às vezes, você não poderá ajustar o arquivo inteiro na memória. Nesse caso, você terá que ler trechos de 4096 bytes sequencialmente e alimentá-los com o md5método:

import hashlib
def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

Nota: hash_md5.hexdigest() retornará a representação de sequência hexadecimal para o resumo, se você precisar apenas dos bytes compactados return hash_md5.digest(), para que não precise converter novamente.

quantumSoup
fonte
297

Existe uma maneira que é bastante ineficiente de memória .

único arquivo:

import hashlib
def file_as_bytes(file):
    with file:
        return file.read()

print hashlib.md5(file_as_bytes(open(full_path, 'rb'))).hexdigest()

lista de arquivos:

[(fname, hashlib.md5(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

Lembre-se, porém, que o MD5 é conhecido como quebrado e não deve ser usado para nenhuma finalidade, pois a análise de vulnerabilidade pode ser realmente complicada, e é impossível analisar qualquer possível uso futuro ao qual seu código possa ser usado para problemas de segurança. IMHO, ele deve ser totalmente removido da biblioteca para que todos que o usem sejam forçados a atualizar. Então, aqui está o que você deve fazer:

[(fname, hashlib.sha256(file_as_bytes(open(fname, 'rb'))).digest()) for fname in fnamelst]

Se você quiser apenas 128 bits de resumo, você pode fazer .digest()[:16].

Isso fornecerá uma lista de tuplas, cada uma contendo o nome de seu arquivo e seu hash.

Novamente, questiono fortemente o uso do MD5. Você deve pelo menos usar o SHA1 e, dadas as falhas recentes descobertas no SHA1 , provavelmente nem isso. Algumas pessoas pensam que, desde que você não esteja usando o MD5 para fins 'criptográficos', você estará bem. Mas as coisas tendem a acabar tendo um escopo mais amplo do que o esperado inicialmente, e sua análise de vulnerabilidade casual pode ser completamente falha. É melhor simplesmente adquirir o hábito de usar o algoritmo certo imediatamente. É só digitar um monte diferente de letras. Não é tão difícil.

Aqui está uma maneira mais complexa, mas eficiente em termos de memória :

import hashlib

def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
    for block in bytesiter:
        hasher.update(block)
    return hasher.hexdigest() if ashexstr else hasher.digest()

def file_as_blockiter(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block
            block = afile.read(blocksize)


[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.md5()))
    for fname in fnamelst]

E, novamente, como o MD5 está quebrado e realmente não deve mais ser usado:

[(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.sha256()))
    for fname in fnamelst]

Mais uma vez, você pode fazer [:16]a chamada depois hash_bytestr_iter(...)se quiser apenas 128 bits digeridos.

Omniforme
fonte
66
Só estou usando o MD5 para confirmar que o arquivo não está corrompido. Eu não estou tão preocupado com isso ser quebrado.
Alexander Alexander
87
@TheLifelessOne: E, apesar dos avisos assustadores e onívoros, esse é um bom uso do MD5.
Presidente James K. Polk
22
@GregS, @TheLifelessOne - Sim, e a próxima coisa que você sabe é que alguém encontra uma maneira de usar esse fato sobre seu aplicativo para fazer com que um arquivo seja aceito como incorrupto quando não é o arquivo que você espera. Não, eu mantenho meus avisos assustadores. Eu acho que o MD5 deve ser removido ou vir com avisos de descontinuação.
omniforme
10
Eu provavelmente usaria .hexdigest () em vez de .digest () - é mais fácil para os humanos lerem - qual é o objetivo do OP.
Zbstof 25/09/12
21
Eu usei essa solução, mas deu incorretamente o mesmo hash para dois arquivos PDF diferentes. A solução foi abrir os arquivos especificando o modo binário, ou seja: [(fname, hashlib.md5 (open (fname, 'rb' ) .read ()). Hexdigest ()) para fname in fnamelst] Isso está mais relacionado para a função open que md5, mas achei que seria útil denunciá-lo, devido ao requisito de compatibilidade entre plataformas indicado acima (consulte também: docs.python.org/2/tutorial/… ).
26413 BlueCoder
34

Claramente, não estou adicionando nada fundamentalmente novo, mas adicionei esta resposta antes de comentar o status, além das regiões de código tornarem as coisas mais claras - de qualquer maneira, especificamente para responder à pergunta de @ Nemo da resposta de Omnifarious:

Por acaso, pensei um pouco sobre as somas de verificação (vim aqui procurando sugestões sobre tamanhos de bloco, especificamente) e descobri que esse método pode ser mais rápido do que você esperaria. Tomando o mais rápido (mas bastante típico) timeit.timeitou /usr/bin/timeresultado de cada um dos vários métodos de checksum um arquivo de aprox. 11MB:

$ ./sum_methods.py
crc32_mmap(filename) 0.0241742134094
crc32_read(filename) 0.0219960212708
subprocess.check_output(['cksum', filename]) 0.0553209781647
md5sum_mmap(filename) 0.0286180973053
md5sum_read(filename) 0.0311000347137
subprocess.check_output(['md5sum', filename]) 0.0332629680634
$ time md5sum /tmp/test.data.300k
d3fe3d5d4c2460b5daacc30c6efbc77f  /tmp/test.data.300k

real    0m0.043s
user    0m0.032s
sys     0m0.010s
$ stat -c '%s' /tmp/test.data.300k
11890400

Portanto, parece que o Python e / usr / bin / md5sum levam cerca de 30ms para um arquivo de 11MB. A md5sumfunção relevante ( md5sum_readna lista acima) é bastante semelhante à Omnifarious:

import hashlib
def md5sum(filename, blocksize=65536):
    hash = hashlib.md5()
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            hash.update(block)
    return hash.hexdigest()

É verdade que estas são de execuções únicas ( mmapas que são sempre um pouco mais rápidas quando são feitas pelo menos algumas dúzias de execuções), e as minhas geralmente recebem um extra f.read(blocksize)depois que o buffer é esgotado, mas é razoavelmente repetível e mostra que md5sumna linha de comando está não necessariamente mais rápido que uma implementação em Python ...

EDIT: Desculpe pelo longo atraso, não olhei para isso há algum tempo, mas para responder à pergunta de @ EdRandall, vou escrever uma implementação do Adler32. No entanto, eu não corri os parâmetros para isso. É basicamente o mesmo que o CRC32 teria sido: em vez das chamadas init, update e digest, tudo é uma zlib.adler32()chamada:

import zlib
def adler32sum(filename, blocksize=65536):
    checksum = zlib.adler32("")
    with open(filename, "rb") as f:
        for block in iter(lambda: f.read(blocksize), b""):
            checksum = zlib.adler32(block, checksum)
    return checksum & 0xffffffff

Observe que isso deve começar com a string vazia, pois as somas de Adler realmente diferem quando se inicia do zero em relação à sua soma para "", que é 1- o CRC pode começar com 0ele. O AND-ing é necessário para torná-lo um número inteiro não assinado de 32 bits, o que garante que ele retorne o mesmo valor nas versões do Python.

rsandwick3
fonte
Você poderia adicionar algumas linhas comparando o SHA1 e também o zlib.adler32, talvez?
EdRandall #
11
A função md5sum () acima pressupõe que você tenha acesso de gravação ao arquivo. Se você substituir "r + b" na chamada open () por "rb", funcionará bem.
21815 Kevin Murrda
11
@ EdRandall: adler32 realmente não vale a pena se preocupar, por exemplo. leviathansecurity.com/blog/analysis-of-adler32
MikeW
6

No Python 3.8 ou superior, você pode fazer

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

Considere usar em hashlib.blake2bvez de md5(basta substituí md5-lo blake2bno snippet acima). É criptograficamente seguro e mais rápido que o MD5.

Boris
fonte
O :=operador é um "operador de atribuição" (novo no Python 3.8+); permite atribuir valores dentro de uma expressão maior; mais informações aqui: docs.python.org/3/whatsnew/3.8.html#assignment-expressions
Benjamin
0
hashlib.md5(pathlib.Path('path/to/file').read_bytes()).hexdigest()
Johnson
fonte
3
Oi! Por favor, adicione alguma explicação ao seu código sobre por que essa é uma solução para o problema. Além disso, este post é bastante antigo, portanto, você também deve adicionar algumas informações sobre por que sua solução adiciona algo que os outros ainda não abordaram.
precisa saber é o seguinte
11
É uma outra maneira ineficiente de memória
Erik Aronesty
-2

Eu acho que confiar no invoke package e no md5sum binário é um pouco mais conveniente do que o subprocesso ou o pacote md5

import invoke

def get_file_hash(path):

    return invoke.Context().run("md5sum {}".format(path), hide=True).stdout.split(" ")[0]

Obviamente, isso pressupõe que você invocou e md5sum instalado.

Puchatek
fonte
3
Se pathfor um caminho fornecido pelo usuário, isso permitirá que qualquer usuário execute comandos bash arbitrários no seu sistema.
Boris