Linux: calcular um único hash para uma determinada pasta e conteúdo?

95

Certamente deve haver uma maneira de fazer isso facilmente!

Eu tentei os aplicativos de linha de comando do Linux como sha1sume, md5summas eles parecem apenas ser capazes de calcular hashes de arquivos individuais e gerar uma lista de valores de hash, um para cada arquivo.

Preciso gerar um único hash para todo o conteúdo de uma pasta (não apenas os nomes dos arquivos).

Eu gostaria de fazer algo como

sha1sum /folder/of/stuff > singlehashvalue

Editar: para esclarecer, meus arquivos estão em vários níveis em uma árvore de diretório, eles não estão todos na mesma pasta raiz.

Ben L
fonte
1
Por 'conteúdo inteiro', você quer dizer os dados lógicos de todos os arquivos no diretório ou seus dados junto com meta enquanto chegam ao hash raiz? Como os critérios de seleção do seu caso de uso são bastante amplos, tentei abordar alguns critérios práticos em minha resposta.
six-k

Respostas:

123

Uma maneira possível seria:

caminho sha1sum / para / pasta / * | sha1sum

Se houver uma árvore de diretórios inteira, é melhor você usar find e xargs. Um comando possível seria

localizar caminho / para / pasta -tipo f -print0 | sort -z | xargs -0 sha1sum | sha1sum

E, finalmente, se você também precisa levar em conta as permissões e diretórios vazios:

(find path/to/folder -type f -print0  | sort -z | xargs -0 sha1sum;
 find path/to/folder \( -type f -o -type d \) -print0 | sort -z | \
   xargs -0 stat -c '%n %a') \
| sha1sum

Os argumentos para statfarão com que ele imprima o nome do arquivo, seguido por suas permissões octais. As duas descobertas serão executadas uma após a outra, causando o dobro da quantidade de E / S do disco, a primeira encontrando todos os nomes de arquivos e fazendo a soma de verificação do conteúdo, a segunda encontrando todos os nomes de arquivos e diretórios, imprimindo nome e modo. A lista de "nomes de arquivos e somas de verificação", seguida por "nomes e diretórios, com permissões", terá a soma de verificação para uma soma de verificação menor.

Vatine
fonte
2
e não se esqueça de definir LC_ALL = POSIX, para que as várias ferramentas criem uma saída independente do local.
David Schmitt
2
Eu encontrei gato | sha1sum é consideravelmente mais rápido que sha1sum | sha1sum. YMMV, experimente cada um destes em seu sistema: hora de localizar caminho / para / pasta -tipo f -print0 | sort -z | xargs -0 sha1sum | sha1sum; hora encontrar caminho / para / pasta -tipo f -print0 | sort -z | xargs -0 cat | sha1sum
Bruno Bronosky
5
@RichardBronosky - Vamos supor que temos dois arquivos, A e B. A contém "foo" e B contém "bar estava aqui". Com seu método, não seríamos capazes de separar isso dos dois arquivos C e D, onde C contém "foobar" e D contém "estava aqui". Fazendo o hash de cada arquivo individualmente e depois o hash de todos os pares de "hash de nome de arquivo", podemos ver a diferença.
Vatine
2
Para fazer isso funcionar independentemente do caminho do diretório (ou seja, quando você deseja comparar os hashes de duas pastas diferentes), você precisa usar um caminho relativo e mudar para o diretório apropriado, porque os caminhos estão incluídos no hash final:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles
3
@robbles Isso está correto e porque eu não coloquei uma inicial /na path/to/folderbroca.
Vatine de
25
  • Use uma ferramenta de detecção de intrusão no sistema de arquivos como um assistente .

  • hash um tar ball do diretório:

    tar cvf - /path/to/folder | sha1sum

  • Codifique algo você mesmo, como oneliner de vatine :

    find /path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

David Schmitt
fonte
3
+1 para a solução de alcatrão. Isso é o mais rápido, mas abandonar o v. Verbosidade apenas o torna mais lento.
Bruno Bronosky
6
observe que a solução tar assume que os arquivos estão na mesma ordem quando você os compara. Se eles estão ou não, depende do sistema de arquivos em que os arquivos residem ao fazer a comparação.
nos
5
O hash git não é adequado para esse propósito, pois o conteúdo do arquivo é apenas uma parte de sua entrada. Mesmo para o commit inicial de um branch, o hash é afetado pela mensagem de commit e pelos metadados de commit, como o tempo do commit. Se você confirmar a mesma estrutura de diretório várias vezes, obterá um hash diferente a cada vez; portanto, o hash resultante não é adequado para determinar se dois diretórios são cópias exatas um do outro, enviando apenas o hash.
Zoltan
1
@Zoltan o hash git está perfeitamente bem, se você usar um hash de árvore e não um hash de commit.
hobbs
@hobbs A resposta originalmente declarada "commit hash", que certamente não é adequada para este propósito. O hash da árvore parece um candidato muito melhor, mas ainda pode haver armadilhas ocultas. Um que me vem à mente é que ter o bit executável definido em alguns arquivos altera o hash da árvore. Você tem que emitir git config --local core.fileMode falseantes de se comprometer para evitar isso. Não sei se há mais advertências como esta.
Zoltan
14

Você pode fazer tar -c /path/to/folder | sha1sum

S.Lott
fonte
16
Se você quiser replicar essa soma de verificação em uma máquina diferente, o tar pode não ser uma boa escolha, já que o formato parece ter espaço para ambigüidade e existe em muitas versões, então o tar em outra máquina pode produzir saídas diferentes dos mesmos arquivos.
slowdog
2
preocupações válidas de slowdog não obstante, se você se preocupa com o conteúdo do arquivo, permissões, etc., mas não modificação do tempo, você pode adicionar a --mtimeopção da seguinte forma: tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
Binário Phile
@ S.Lott se o tamanho do diretório for grande, quero dizer, se o tamanho do diretório for tão grande, compactá-lo e colocar o md5 nele levará mais tempo
Kasun Siyambalapitiya
13

Se você quiser apenas verificar se algo mudou na pasta, recomendo este:

ls -alR --full-time /folder/of/stuff | sha1sum

Ele apenas fornecerá um hash da saída do ls, que contém pastas, subpastas, seus arquivos, seu carimbo de data / hora, tamanho e permissões. Praticamente tudo o que você precisa para determinar se algo mudou.

Observe que este comando não gerará hash para cada arquivo, mas é por isso que deve ser mais rápido do que usar find.

Shumoapp
fonte
1
Não sei por que isso não tem mais votos positivos dada a simplicidade da solução. Alguém pode explicar por que isso não funcionaria bem?
Dave C
1
Suponho que isso não seja o ideal, pois o hash gerado será baseado no proprietário do arquivo, configuração do formato de data, etc.
Ryota
1
O comando ls pode ser personalizado para produzir o que você quiser. Você pode substituir -l por -gG para omitir o grupo e o proprietário. E você pode alterar o formato da data com a opção --time-style. Basicamente, verifique a página de manual do ls e veja o que atende às suas necessidades.
Shumoapp
@DaveC Porque é praticamente inútil. Se você quiser comparar nomes de arquivos, basta compará-los diretamente. Eles não são tão grandes.
Navin de
7
@Navin Pela pergunta, não está claro se é necessário fazer o hash do conteúdo do arquivo ou detectar uma mudança em uma árvore. Cada caso tem seus usos. Armazenar nomes de arquivo de 45K em uma árvore de kernel, por exemplo, é menos prático do que um único hash. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum funciona muito bem para mim
yashma de
5

Uma abordagem robusta e limpa

  • Em primeiro lugar, não monopolize a memória disponível ! Faça hash de um arquivo em partes, em vez de alimentar o arquivo inteiro.
  • Abordagens diferentes para necessidades / objetivos diferentes (todos os itens abaixo ou escolha o que for aplicável):
    • Hash apenas o nome da entrada de todas as entradas na árvore de diretórios
    • Hash o conteúdo do arquivo de todas as entradas (deixando o meta como, número do inode, ctime, atime, mtime, tamanho, etc., você entendeu)
    • Para um link simbólico, seu conteúdo é o nome referente. Hash ou opte por pular
    • Siga ou não siga (nome resolvido) o link simbólico enquanto faz o hash do conteúdo da entrada
    • Se for um diretório, seu conteúdo será apenas entradas de diretório. Durante a passagem recursiva, eles terão um hash eventualmente, mas os nomes de entrada de diretório desse nível devem ser hash para marcar este diretório? Útil em casos de uso em que o hash é necessário para identificar uma alteração rapidamente, sem ter que ir fundo para o hash do conteúdo. Um exemplo seria a mudança de nome de um arquivo, mas o resto do conteúdo permanece o mesmo e são todos arquivos bastante grandes
    • Lide bem com arquivos grandes (mais uma vez, lembre-se da RAM)
    • Lidar com árvores de diretório muito profundas (lembre-se dos descritores de arquivo abertos)
    • Lidar com nomes de arquivo não padrão
    • Como proceder com arquivos que são sockets, pipes / FIFOs, dispositivos de bloco, dispositivos char? Deve hash-los também?
    • Não atualize o tempo de acesso de nenhuma entrada durante a travessia, pois isso será um efeito colateral e contraproducente (intuitivo?) Para certos casos de uso.

Isso é o que eu tenho em cima da minha cabeça, qualquer um que passou algum tempo trabalhando nisso praticamente teria pegado outras pegadinhas e casos difíceis.

Aqui está uma ferramenta , muito leve na memória, que atende a maioria dos casos, pode ser um pouco áspera nas bordas, mas tem sido bastante útil.

Um exemplo de uso e saída de dtreetrawl.

Usage:
  dtreetrawl [OPTION...] "/trawl/me" [path2,...]

Help Options:
  -h, --help                Show help options

Application Options:
  -t, --terse               Produce a terse output; parsable.
  -j, --json                Output as JSON
  -d, --delim=:             Character or string delimiter/separator for terse output(default ':')
  -l, --max-level=N         Do not traverse tree beyond N level(s)
  --hash                    Enable hashing(default is MD5).
  -c, --checksum=md5        Valid hashing algorithms: md5, sha1, sha256, sha512.
  -R, --only-root-hash      Output only the root hash. Blank line if --hash is not set
  -N, --no-name-hash        Exclude path name while calculating the root checksum
  -F, --no-content-hash     Do not hash the contents of the file
  -s, --hash-symlink        Include symbolic links' referent name while calculating the root checksum
  -e, --hash-dirent         Include hash of directory entries while calculating root checksum

Um snippet de saída amigável:

...
... //clipped
...
/home/lab/linux-4.14-rc8/CREDITS
        Base name                    : CREDITS
        Level                        : 1
        Type                         : regular file
        Referent name                :
        File size                    : 98443 bytes
        I-node number                : 290850
        No. directory entries        : 0
        Permission (octal)           : 0644
        Link count                   : 1
        Ownership                    : UID=0, GID=0
        Preferred I/O block size     : 4096 bytes
        Blocks allocated             : 200
        Last status change           : Tue, 21 Nov 17 21:28:18 +0530
        Last file access             : Thu, 28 Dec 17 00:53:27 +0530
        Last file modification       : Tue, 21 Nov 17 21:28:18 +0530
        Hash                         : 9f0312d130016d103aa5fc9d16a2437e

Stats for /home/lab/linux-4.14-rc8:
        Elapsed time     : 1.305767 s
        Start time       : Sun, 07 Jan 18 03:42:39 +0530
        Root hash        : 434e93111ad6f9335bb4954bc8f4eca4
        Hash type        : md5
        Depth            : 8
        Total,
                size           : 66850916 bytes
                entries        : 12484
                directories    : 763
                regular files  : 11715
                symlinks       : 6
                block devices  : 0
                char devices   : 0
                sockets        : 0
                FIFOs/pipes    : 0
seis-k
fonte
1
Você pode dar um breve exemplo para obter um sha256 robusto e limpo de uma pasta, talvez para uma pasta do Windows com três subdiretórios e alguns arquivos em cada um?
Ferit
3

Se você quiser apenas hash o conteúdo dos arquivos, ignorando os nomes dos arquivos, então você pode usar

cat $FILES | md5sum

Certifique-se de ter os arquivos na mesma ordem ao calcular o hash:

cat $(echo $FILES | sort) | md5sum

Mas você não pode ter diretórios em sua lista de arquivos.


fonte
2
Mover o final de um arquivo para o início do arquivo que o segue em ordem alfabética não afetaria o hash, mas deveria. Um delimitador de arquivo ou comprimentos de arquivo precisam ser incluídos no hash.
Jason Stangroome
3

Outra ferramenta para conseguir isso:

http://md5deep.sourceforge.net/

Como parece: como md5sum, mas também recursivo, além de outros recursos.

Jack
fonte
1
Embora este link possa responder à pergunta, é melhor incluir as partes essenciais da resposta aqui e fornecer o link para referência. As respostas somente com link podem se tornar inválidas se a página vinculada mudar.
Mamoun Benghezal
3

Se este for um repositório git e você quiser ignorar todos os arquivos .gitignore, você pode querer usar isto:

git ls-files <your_directory> | xargs sha256sum | cut -d" " -f1 | sha256sum | cut -d" " -f1

Isso está funcionando bem para mim.

ndbroadbent
fonte
Muito obrigado! :)
visortelle
Para muitas aplicações, essa abordagem é superior. O hash apenas dos arquivos de código-fonte obtém um hash suficientemente exclusivo em muito menos tempo.
John McGehee
1

Tente fazer isso em duas etapas:

  1. crie um arquivo com hashes para todos os arquivos em uma pasta
  2. hash este arquivo

Igual a:

# for FILE in `find /folder/of/stuff -type f | sort`; do sha1sum $FILE >> hashes; done
# sha1sum hashes

Ou faça tudo de uma vez:

# cat `find /folder/of/stuff -type f | sort` | sha1sum
Joao da silva
fonte
for F in 'find ...' ...não funciona quando você tem espaços nos nomes (o que você sempre faz hoje em dia).
mivk de
1

Eu canalizaria os resultados para arquivos individuais sort(para evitar uma mera reorganização de arquivos para alterar o hash) para md5sumou sha1sum, o que você escolher.

Rafał Dowgird
fonte
1

Eu escrevi um script Groovy para fazer isso:

import java.security.MessageDigest

public static String generateDigest(File file, String digest, int paddedLength){
    MessageDigest md = MessageDigest.getInstance(digest)
    md.reset()
    def files = []
    def directories = []

    if(file.isDirectory()){
        file.eachFileRecurse(){sf ->
            if(sf.isFile()){
                files.add(sf)
            }
            else{
                directories.add(file.toURI().relativize(sf.toURI()).toString())
            }
        }
    }
    else if(file.isFile()){
        files.add(file)
    }

    files.sort({a, b -> return a.getAbsolutePath() <=> b.getAbsolutePath()})
    directories.sort()

    files.each(){f ->
        println file.toURI().relativize(f.toURI()).toString()
        f.withInputStream(){is ->
            byte[] buffer = new byte[8192]
            int read = 0
            while((read = is.read(buffer)) > 0){
                md.update(buffer, 0, read)
            }
        }
    }

    directories.each(){d ->
        println d
        md.update(d.getBytes())
    }

    byte[] digestBytes = md.digest()
    BigInteger bigInt = new BigInteger(1, digestBytes)
    return bigInt.toString(16).padLeft(paddedLength, '0')
}

println "\n${generateDigest(new File(args[0]), 'SHA-256', 64)}"

Você pode personalizar o uso para evitar a impressão de cada arquivo, alterar o resumo da mensagem, remover o hash do diretório, etc. Eu testei com os dados de teste do NIST e funciona conforme o esperado. http://www.nsrl.nist.gov/testdata/

gary-macbook:Scripts garypaduana$ groovy dirHash.groovy /Users/garypaduana/.config
.DS_Store
configstore/bower-github.yml
configstore/insight-bower.json
configstore/update-notifier-bower.json
filezilla/filezilla.xml
filezilla/layout.xml
filezilla/lockfile
filezilla/queue.sqlite3
filezilla/recentservers.xml
filezilla/sitemanager.xml
gtk-2.0/gtkfilechooser.ini
a/
configstore/
filezilla/
gtk-2.0/
lftp/
menus/
menus/applications-merged/

79de5e583734ca40ff651a3d9a54d106b52e94f1f8c2cd7133ca3bbddc0c6758
não verificado
fonte
1

Eu tive que verificar em um diretório inteiro para alterações de arquivo.

Mas excluindo, timestamps, propriedades de diretório.

O objetivo é obter uma soma idêntica em qualquer lugar, se os arquivos forem idênticos.

Incluindo hospedados em outras máquinas, independentemente de qualquer coisa, exceto os arquivos, ou uma mudança neles.

md5sum * | md5sum | cut -d' ' -f1

Ele gera uma lista de hash por arquivo e, em seguida, concatena esses hashes em um.

Isso é muito mais rápido do que o método tar.

Para maior privacidade em nossos hashes, podemos usar sha512sum na mesma receita.

sha512sum * | sha512sum | cut -d' ' -f1

Os hashes também são idênticos em qualquer lugar usando sha512sum, mas não há maneira conhecida de revertê-lo.

NVRM
fonte
Isso parece muito mais simples do que a resposta aceita para hash de um diretório. Eu não estava achando a resposta aceita confiável. Um problema ... há alguma chance de os hashes saírem em uma ordem diferente? sha256sum /tmp/thd-agent/* | sorté o que estou tentando para um pedido confiável, então apenas hash.
thinktt
Olá, parece que os hashes vêm em ordem alfabética por padrão. O que você quer dizer com pedido confiável? Você tem que organizar tudo isso sozinho. Por exemplo, usando matrizes associativas, entrada + hash. Em seguida, você classifica esse array por entrada, o que fornece uma lista de hashes computados na ordem de classificação. Eu acredito que você pode usar um objeto json de outra forma, e hash o objeto inteiro diretamente.
NVRM
Se bem entendi, você está dizendo que ele faz o hash dos arquivos em ordem alfabética. Isso parece certo. Algo na resposta aceita acima estava me dando ordens diferentes intermitentes às vezes, então estou apenas tentando garantir que isso não aconteça novamente. Vou ficar com a classificação no final. Parece estar funcionando. O único problema com este método vs resposta aceita que vejo é que ele não lida com pastas aninhadas. No meu caso, não tenho pastas, então funciona muito bem.
thinktt
o que dizer ls -r | sha256sum?
NVRM
@NVRM tentou e apenas verificou as alterações no nome do arquivo, não no conteúdo do arquivo
Gi0rgi0s
0

Você poderia sha1sumgerar a lista de valores hash e, em seguida, sha1sumessa lista novamente, isso depende do que exatamente você deseja realizar.

Ronny Vindenes
fonte
0

Aqui está uma variante simples e curta do Python 3 que funciona bem para arquivos de pequeno porte (por exemplo, uma árvore de origem ou algo assim, onde cada arquivo individualmente pode caber na RAM facilmente), ignorando diretórios vazios, com base nas idéias de outras soluções:

import os, hashlib

def hash_for_directory(path, hashfunc=hashlib.sha1):                                                                                            
    filenames = sorted(os.path.join(dp, fn) for dp, _, fns in os.walk(path) for fn in fns)         
    index = '\n'.join('{}={}'.format(os.path.relpath(fn, path), hashfunc(open(fn, 'rb').read()).hexdigest()) for fn in filenames)               
    return hashfunc(index.encode('utf-8')).hexdigest()                          

Funciona assim:

  1. Encontre todos os arquivos no diretório recursivamente e classifique-os por nome
  2. Calcule o hash (padrão: SHA-1) de cada arquivo (lê o arquivo inteiro na memória)
  3. Faça um índice textual com linhas "filename = hash"
  4. Codifique esse índice de volta em uma string de bytes UTF-8 e faça o hash

Você pode passar uma função hash diferente como segundo parâmetro se SHA-1 não for sua preferência.

Thomas Perl
fonte