Como obtenho a soma MD5 do conteúdo de um diretório como uma soma?

Como obtenho a soma MD5 do conteúdo de um diretório como uma soma?

O programa md5sum não fornece somas de verificação para diretórios. Quero obter uma única soma de verificação MD5 para todo o conteúdo de um diretório, incluindo arquivos em subdiretórios. Ou seja, uma soma de verificação combinada feita de todos os arquivos. Existe uma maneira de fazer isso?

Responder1

A maneira certa depende exatamente do motivo pelo qual você está perguntando:

Opção 1: comparar apenas dados

Se você só precisa de um hash do conteúdo do arquivo da árvore, isso resolverá o problema:

$ find -s somedir -type f -exec md5sum {} \; | md5sum

Isso primeiro resume todo o conteúdo do arquivo individualmente, em uma ordem previsível, depois passa a lista de nomes de arquivos e hashes MD5 para o próprio hash, fornecendo um único valor que só muda quando o conteúdo de um dos arquivos na árvore muda.

Infelizmente, find -ssó funciona com BSD find(1), usado em macOS, FreeBSD, NetBSD e OpenBSD. Para obter algo comparável em um sistema com GNU ou SUS find(1), você precisa de algo um pouco mais feio:

$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum

Imitamos o comportamento do BSD find -sadicionando uma chamada para sort. O -k 2bit diz para pular o hash MD5, então ele classifica apenas os nomes dos arquivos, que estão no campo 2 até o final da linha pelo sortcálculo de '.

Há um ponto fraco nesta versão do comando: ele pode ficar confuso se você tiver nomes de arquivos com novas linhas, porque a chamada parecerá com várias linhas sort. A find -svariante não tem esse problema, pois o percurso e a classificação da árvore acontecem dentro do mesmo programa find.

Em ambos os casos, a classificação é necessária para evitar falsos positivos: os sistemas de arquivos Unix/Linux mais comuns não mantêm as listagens de diretórios em uma ordem estável e previsível. Você pode não perceber isso usando lse tal, que classifica silenciosamente o conteúdo do diretório para você. Chamar findsem classificar sua saída de alguma forma fará com que a ordem das linhas na saída corresponda à ordem que o sistema de arquivos subjacente as retorna, o que fará com que este comando forneça um valor de hash alterado se a ordem dos arquivos fornecidos a ele como entrada mudar, mesmo que os dados permaneçam idênticos.

Você pode perguntar se o -k 2bit no sortcomando GNU acima é necessário. Dado que o hash dos dados do arquivo é um proxy adequado para o nome do arquivo, desde que o conteúdo não tenha mudado, não obteremos falsos positivos se abandonarmos esta opção, permitindo-nos usar o mesmo comando com GNU e BSD sort. No entanto, perceba que há uma pequena chance (1:2 128 com MD5) de que a ordem exata dos nomes dos arquivos não corresponda à ordem parcial que a ausência -k 2pode fornecer se houver uma colisão de hash. Tenha em mente, entretanto, que se essas pequenas chances de incompatibilidade forem importantes para sua aplicação, toda essa abordagem provavelmente estará fora de questão para você.

Pode ser necessário alterar os md5sumcomandos para md5ou alguma outra função hash. Se você escolher outra função hash e precisar da segunda forma do comando para o seu sistema, talvez seja necessário ajustar o sortcomando de acordo. Outra armadilha é que alguns programas de soma de dados não escrevem nenhum nome de arquivo, sendo um excelente exemplo o antigo sumprograma Unix.

Este método é um tanto ineficiente, chamando md5sumN+1 vezes, onde N é o número de arquivos na árvore, mas esse é um custo necessário para evitar hash de metadados de arquivos e diretórios.

Opção 2: comparar dadoseMetadados

Se você precisa ser capaz de detectar issoqualquer coisaem uma árvore mudou, não apenas o conteúdo do arquivo, peça tarpara empacotar o conteúdo do diretório para você e envie-o para md5sum:

$ tar -cf - somedir | md5sum

Como tartambém vê permissões de arquivo, propriedade, etc., isso também detectará alterações nessas coisas, não apenas alterações no conteúdo do arquivo.

Este método é consideravelmente mais rápido, pois faz apenas uma passagem pela árvore e executa o programa hash apenas uma vez.

Tal como acontece com o findmétodo baseado acima, tarprocessará os nomes dos arquivos na ordem em que o sistema de arquivos subjacente os retorna. Pode ser que em seu aplicativo você tenha certeza de que isso não acontecerá. Posso pensar em pelo menos três padrões de uso diferentes onde esse provavelmente será o caso. (Não vou listá-los, porque estamos entrando em um território de comportamento não especificado. Cada sistema de arquivos pode ser diferente aqui, mesmo de uma versão do sistema operacional para outra.)

Se você receber falsos positivos, recomendo escolher a find | cpioopção emResposta de Gilles.

Responder2

A soma de verificação precisa ser uma representação determinística e inequívoca dos arquivos como uma string. Determinístico significa que se você colocar os mesmos arquivos nos mesmos locais, obterá o mesmo resultado. Inequívoco significa que dois conjuntos diferentes de arquivos têm representações diferentes.

Dados e metadados

Criar um arquivo contendo os arquivos é um bom começo. Esta é uma representação inequívoca (obviamente, já que você pode recuperar os arquivos extraindo o arquivo). Pode incluir metadados de arquivo, como datas e propriedade. No entanto, isto ainda não está certo: um arquivo é ambíguo, porque a sua representação depende da ordem em que os ficheiros são armazenados e, se aplicável, da compressão.

Uma solução é classificar os nomes dos arquivos antes de arquivá-los. Se os nomes dos seus arquivos não contiverem novas linhas, você poderá find | sortlistá-los e adicioná-los ao arquivo nesta ordem. Tome cuidado para informar ao arquivador para não recorrer aos diretórios. Aqui estão exemplos com POSIX pax, GNU tar e cpio:

find | LC_ALL=C sort | pax -w -d | md5sum
find | LC_ALL=C sort | tar -cf - -T - --no-recursion | md5sum
find | LC_ALL=C sort | cpio -o | md5sum

Somente nomes e conteúdos, do jeito low-tech

Se você deseja levar em consideração apenas os dados do arquivo e não os metadados, você pode fazer um arquivo que inclua apenas o conteúdo do arquivo, mas não existem ferramentas padrão para isso. Em vez de incluir o conteúdo do arquivo, você pode incluir o hash dos arquivos. Se os nomes dos arquivos não contiverem novas linhas e houver apenas arquivos e diretórios regulares (sem links simbólicos ou arquivos especiais), isso será bastante fácil, mas você precisará cuidar de algumas coisas:

{ export LC_ALL=C;
  find -type f -exec wc -c {} \; | sort; echo;
  find -type f -exec md5sum {} + | sort; echo;
  find . -type d | sort; find . -type d | sort | md5sum;
} | md5sum

Incluímos uma listagem de diretórios além da lista de somas de verificação, caso contrário os diretórios vazios seriam invisíveis. A lista de arquivos é classificada (em um local específico e reproduzível — obrigado a Peter.O por me lembrar disso). echosepara as duas partes (sem isso, você poderia criar alguns diretórios vazios cujo nome parecesse md5sumuma saída que também poderia passar por arquivos comuns). Também incluímos uma lista de tamanhos de arquivo, para evitarataques de extensão de comprimento.

A propósito, o MD5 está obsoleto. Se estiver disponível, considere usar SHA-2 ou pelo menos SHA-1.

Nomes e dados, suportando novas linhas em nomes

Aqui está uma variante do código acima que depende de ferramentas GNU para separar os nomes dos arquivos com bytes nulos. Isso permite que os nomes dos arquivos contenham novas linhas. Os utilitários GNU digest citam caracteres especiais em sua saída, para que não haja novas linhas ambíguas.

{ export LC_ALL=C;
  du -0ab | sort -z; # file lengths, including directories (with length 0)
  echo | tr '\n' '\000'; # separator
  find -type f -exec sha256sum {} + | sort -z; # file hashes
  echo | tr '\n' '\000'; # separator
  echo "End of hashed data."; # End of input marker
} | sha256sum

Uma abordagem mais robusta

Aqui está um script Python minimamente testado que cria um hash descrevendo uma hierarquia de arquivos. Ele leva diretórios e conteúdo de arquivos para contas e ignora links simbólicos e outros arquivos, e retorna um erro fatal se algum arquivo não puder ser lido.

#! /usr/bin/env python
import hashlib, hmac, os, stat, sys
## Return the hash of the contents of the specified file, as a hex string
def file_hash(name):
    f = open(name)
    h = hashlib.sha256()
    while True:
        buf = f.read(16384)
        if len(buf) == 0: break
        h.update(buf)
    f.close()
    return h.hexdigest()
## Traverse the specified path and update the hash with a description of its
## name and contents
def traverse(h, path):
    rs = os.lstat(path)
    quoted_name = repr(path)
    if stat.S_ISDIR(rs.st_mode):
        h.update('dir ' + quoted_name + '\n')
        for entry in sorted(os.listdir(path)):
            traverse(h, os.path.join(path, entry))
    elif stat.S_ISREG(rs.st_mode):
        h.update('reg ' + quoted_name + ' ')
        h.update(str(rs.st_size) + ' ')
        h.update(file_hash(path) + '\n')
    else: pass # silently symlinks and other special files
h = hashlib.sha256()
for root in sys.argv[1:]: traverse(h, root)
h.update('end\n')
print h.hexdigest()

Responder3

Se o seu objetivo é apenas encontrar diferenças entre dois diretórios, considere usar o diff.

Experimente isto:

diff -qr dir1 dir2

Responder4

Usarchecksumdir:

$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad

Mais rápidoemais fácildo que as outras soluções bash.

informação relacionada