Linux: calcolare un singolo hash per una determinata cartella e contenuto?


95

Sicuramente ci deve essere un modo per farlo facilmente!

Ho provato le app della riga di comando di Linux come sha1sume, md5summa sembrano solo essere in grado di calcolare hash di singoli file e produrre un elenco di valori hash, uno per ogni file.

Devo generare un unico hash per l'intero contenuto di una cartella (non solo i nomi dei file).

Mi piacerebbe fare qualcosa di simile

sha1sum /folder/of/stuff > singlehashvalue

Modifica: per chiarire, i miei file sono a più livelli in un albero di directory, non sono tutti nella stessa cartella principale.


1
Per 'intero contenuto' intendi i dati logici di tutti i file nella directory o dei suoi dati insieme a meta mentre arrivi all'hash radice? Poiché i criteri di selezione del tuo caso d'uso sono piuttosto ampi, ho cercato di affrontarne alcuni pratici nella mia risposta.
six-k

Risposte:


123

Un modo possibile sarebbe:

sha1sum percorso / a / cartella / * | sha1sum

Se c'è un intero albero di directory, probabilmente è meglio usare find e xargs. Un possibile comando sarebbe

trova il percorso / della / cartella -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

E, infine, se devi tenere conto anche dei permessi e delle directory vuote:

(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

Gli argomenti per statfar sì che stampi il nome del file, seguito dai suoi permessi ottali. I due risultati verranno eseguiti uno dopo l'altro, causando il doppio della quantità di I / O del disco, il primo trovando tutti i nomi di file e il controllo del contenuto, il secondo trovando tutti i nomi di file e directory, il nome di stampa e la modalità. L'elenco di "nomi di file e checksum", seguito da "nomi e directory, con permessi" verrà quindi sottoposto a checksum, per un checksum inferiore.


2
e non dimenticare di impostare LC_ALL = POSIX, così i vari strumenti creano un output indipendente dalla localizzazione.
David Schmitt,

2
Ho trovato cat | sha1sum per essere notevolmente più veloce di sha1sum | sha1sum. YMMV, prova ciascuno di questi sul tuo sistema: time find path / to / folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; ora trova percorso / della / cartella -type f -print0 | sort -z | xargs -0 gatto | sha1sum
Bruno Bronosky

5
@RichardBronosky - Supponiamo di avere due file, A e B. A contiene "foo" e B contiene "bar was here". Con il tuo metodo, non saremmo in grado di separarlo da due file C e D, dove C contiene "foobar" e D contiene "era qui". Eseguendo l'hashing di ogni file individualmente e poi l'hash di tutte le coppie di "hash nome file", possiamo vedere la differenza.
Vatine

2
Per fare in modo che funzioni indipendentemente dal percorso della directory (ovvero quando si desidera confrontare gli hash di due cartelle diverse), è necessario utilizzare un percorso relativo e passare alla directory appropriata, poiché i percorsi sono inclusi nell'hash finale:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles

3
@robbles Questo è corretto e perché non ho messo un'iniziale /sul path/to/folderbit.
Vatine

25
  • Utilizzare uno strumento di rilevamento delle intrusioni nel file system come un aiuto .

  • hash un tar ball della directory:

    tar cvf - /path/to/folder | sha1sum

  • Codifica qualcosa tu stesso, come l'oneliner di vatine :

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


3
+1 per la soluzione di catrame. Questo è il più veloce, ma abbassare la verbosità v. Lo rallenta solo.
Bruno Bronosky

6
si noti che la soluzione tar presuppone che i file siano nello stesso ordine quando vengono confrontati. Il fatto che siano dipende dal file system in cui risiedono i file durante il confronto.
n.

5
L'hash git non è adatto a questo scopo poiché il contenuto del file è solo una parte del suo input. Anche per il commit iniziale di un ramo, l'hash è influenzato dal messaggio di commit e anche dai metadati del commit, come l'ora del commit. Se si esegue il commit della stessa struttura di directory più volte, si otterrà un hash diverso ogni volta, quindi l'hash risultante non è adatto per determinare se due directory sono copie esatte l'una dell'altra inviando solo l'hash.
Zoltan

1
@Zoltan, il git hash va benissimo, se usi un tree hash e non un commit hash.
hobbs

@hobbs La risposta originariamente diceva "commit hash", che non è certamente adatto a questo scopo. L'hashish dell'albero sembra un candidato molto migliore, ma potrebbero comunque esserci trappole nascoste. Uno che mi viene in mente è che avere il bit eseguibile impostato su alcuni file cambia l'hash dell'albero. Devi emettere git config --local core.fileMode falseprima di impegnarti per evitarlo. Non so se ci siano altri avvertimenti come questo.
Zoltan

14

Tu puoi fare tar -c /path/to/folder | sha1sum


16
Se vuoi replicare quel checksum su una macchina diversa, tar potrebbe non essere una buona scelta, poiché il formato sembra avere spazio per l'ambiguità ed esiste in molte versioni, quindi il tar su un'altra macchina potrebbe produrre output diverso dagli stessi file.
slowdog

2
preoccupazioni valide di slowdog nonostante, se vi preoccupate per il contenuto dei file, i permessi, ecc, ma non modifica il tempo, è possibile aggiungere l' --mtimeopzione in questo modo: tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
File binario

@ S.Lott se la dimensione della directory è grande, voglio dire se la dimensione della directory è così grande, zipparla e ottenere md5 richiederà più tempo
Kasun Siyambalapitiya

13

Se vuoi solo controllare se qualcosa nella cartella è cambiato, ti consiglio questo:

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

Ti darà solo un hash dell'output ls, che contiene cartelle, sottocartelle, i loro file, il loro timestamp, dimensioni e permessi. Praticamente tutto ciò di cui avresti bisogno per determinare se qualcosa è cambiato.

Tieni presente che questo comando non genererà hash per ogni file, ma è per questo che dovrebbe essere più veloce dell'utilizzo di find.


1
Non sono sicuro del motivo per cui questo non ha più voti positivi data la semplicità della soluzione. Qualcuno può spiegare perché questo non funzionerebbe bene?
Dave C

1
Suppongo che questo non sia l'ideale in quanto l'hash generato sarà basato sul proprietario del file, sull'impostazione del formato della data, ecc.
Ryota

1
Il comando ls può essere personalizzato per visualizzare tutto ciò che desideri. Puoi sostituire -l con -gG per omettere il gruppo e il proprietario. E puoi cambiare il formato della data con l'opzione --time-style. Fondamentalmente controlla la pagina man di ls e vedi cosa si adatta alle tue esigenze.
Shumoapp

@ DaveC Perché è praticamente inutile. Se vuoi confrontare i nomi dei file, confrontali direttamente. Non sono così grandi.
Navin

7
@Navin Dalla domanda non è chiaro se sia necessario eseguire l'hashing del contenuto del file o rilevare una modifica in un albero. Ogni custodia ha i suoi usi. La memorizzazione di 45.000 nomi di file in un albero del kernel, ad esempio, è meno pratica di un singolo hash. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum funziona alla grande per me
yashma

5

Un approccio robusto e pulito

  • Per prima cosa, non sprecare la memoria disponibile ! Eseguire l'hash di un file in blocchi anziché alimentare l'intero file.
  • Approcci diversi per esigenze / scopi diversi (tutti i seguenti o scegli cosa si applica mai):
    • Hash solo il nome della voce di tutte le voci nella struttura della directory
    • Hash il contenuto del file di tutte le voci (lasciando il meta like, numero di inode, ctime, atime, mtime, size, ecc., Hai un'idea)
    • Per un collegamento simbolico, il suo contenuto è il nome di riferimento. Hash o scegli di saltare
    • Seguire o non seguire (nome risolto) il collegamento simbolico durante l'hashing del contenuto della voce
    • Se è una directory, i suoi contenuti sono solo voci di directory. Durante l'attraversamento ricorsivo, verranno eventualmente sottoposti a hashing, ma i nomi delle voci di directory di quel livello dovrebbero essere hash per contrassegnare questa directory? Utile nei casi d'uso in cui è necessario l'hash per identificare rapidamente una modifica senza dover attraversare in profondità per hash il contenuto. Un esempio potrebbe essere il nome di un file che cambia ma il resto del contenuto rimane lo stesso e sono tutti file abbastanza grandi
    • Gestisci bene file di grandi dimensioni (di nuovo, fai attenzione alla RAM)
    • Gestire alberi di directory molto profondi (attenzione ai descrittori di file aperti)
    • Gestisci nomi di file non standard
    • Come procedere con file che sono socket, pipe / FIFO, dispositivi a blocchi, dispositivi char? Bisogna fare hash anche loro?
    • Non aggiornare il tempo di accesso di nessuna voce durante l'attraversamento perché questo sarà un effetto collaterale e controproducente (intuitivo?) Per alcuni casi d'uso.

Questo è quello che ho in testa, chiunque abbia passato un po 'di tempo a lavorare su questo praticamente avrebbe colto altri trucchi e casi d'angolo.

Ecco uno strumento , molto leggero sulla memoria, che risolve la maggior parte dei casi, potrebbe essere un po 'approssimativo ma è stato piuttosto utile.

Un esempio di utilizzo e output di 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

Un frammento di output umano amichevole:

...
... //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

1
Puoi fare un breve esempio per ottenere uno sha256 robusto e pulito di una cartella, magari per una cartella Windows con tre sottodirectory e alcuni file in ciascuna?
Ferit

3

Se vuoi solo eseguire l'hash del contenuto dei file, ignorando i nomi dei file, puoi usare

cat $FILES | md5sum

Assicurati di avere i file nello stesso ordine quando calcoli l'hash:

cat $(echo $FILES | sort) | md5sum

Ma non puoi avere directory nel tuo elenco di file.


2
Spostare la fine di un file all'inizio del file che lo segue in ordine alfabetico non influirà sull'hash ma dovrebbe. Nell'hash dovrebbe essere incluso un delimitatore di file o lunghezze di file.
Jason Stangroome

3

Un altro strumento per raggiungere questo obiettivo:

http://md5deep.sourceforge.net/

Così come suona: come md5sum ma anche ricorsivo, più altre caratteristiche.


1
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere le parti essenziali della risposta qui e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Mamoun Benghezal

3

Se si tratta di un repository git e si desidera ignorare i file in esso contenuti .gitignore, è possibile utilizzarlo:

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

Questo funziona bene per me.


Molte grazie! :)
visortelle

Per molte applicazioni questo approccio è superiore. L'hashing dei soli file del codice sorgente ottiene un hash sufficientemente unico in molto meno tempo.
John McGehee


1

Prova a farlo in due passaggi:

  1. creare un file con hash per tutti i file in una cartella
  2. hash questo file

Così:

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

Oppure fai tutto in una volta:

# cat `find /folder/of/stuff -type f | sort` | sha1sum

for F in 'find ...' ...non funziona quando ci sono spazi nei nomi (cosa che fai sempre oggi).
mivk

1

Vorrei convogliare i risultati per i singoli file attraverso sort(per evitare un semplice riordino dei file per cambiare l'hash) in md5sumo sha1sum, a seconda di quale si sceglie.


1

Ho scritto uno script Groovy per fare questo:

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)}"

È possibile personalizzare l'utilizzo per evitare di stampare ogni file, modificare il digest del messaggio, eliminare l'hashing della directory, ecc. L'ho testato con i dati del test NIST e funziona come previsto. 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

1

Ho dovuto controllare in un'intera directory per le modifiche ai file.

Ma con esclusione, timestamp, proprietà delle directory.

L'obiettivo è ottenere una somma identica ovunque, se i file sono identici.

Incluso ospitato in altre macchine, indipendentemente da qualsiasi cosa tranne i file o da una modifica in essi.

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

Genera un elenco di hash per file, quindi concatena quegli hash in uno solo.

Questo è molto più veloce del metodo tar.

Per una maggiore privacy nei nostri hash, possiamo usare sha512sum sulla stessa ricetta.

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

Anche gli hash sono identici ovunque usando sha512sum ma non esiste un modo noto per invertirlo.


Questo sembra molto più semplice della risposta accettata per l'hashing di una directory. Non trovavo affidabile la risposta accettata. Un problema ... esiste la possibilità che gli hash vengano pubblicati in un ordine diverso? sha256sum /tmp/thd-agent/* | sortè quello che sto cercando per un ordine affidabile, quindi l'ha solo hashing.
thinktt

Ciao, sembra che gli hash siano disponibili in ordine alfabetico per impostazione predefinita. Cosa intendi per ordine affidabile? Devi organizzare tutto da solo. Ad esempio utilizzando array associativi, entry + hash. Quindi ordinate questo array per voce, questo fornisce un elenco di hash calcolati nell'ordinamento. Credo che tu possa usare un oggetto json altrimenti e hash l'intero oggetto direttamente.
NVRM

Se ho capito stai dicendo che esegue l'hashing dei file in ordine alfabetico. Sembra giusto. Qualcosa nella risposta accettata sopra mi dava a volte ordini diversi intermittenti, quindi sto solo cercando di assicurarmi che non accada di nuovo. Continuerò a mettere l'ordinamento alla fine. Sembra funzionare. L'unico problema con questo metodo rispetto alla risposta accettata che vedo è che non si occupa di cartelle nidificate. Nel mio caso non ho cartelle, quindi funziona alla grande.
thinktt

di cosa ls -r | sha256sum?
NVRM

@NVRM l'ha provato e ha appena controllato le modifiche al nome del file, non il contenuto del file
Gi0rgi0s

0

Potresti sha1sumgenerare l'elenco dei valori hash e poi di sha1sumnuovo quell'elenco, dipende da cosa esattamente vuoi ottenere.


0

Ecco una semplice e breve variante in Python 3 che funziona bene per file di piccole dimensioni (ad esempio un albero dei sorgenti o qualcosa del genere, in cui ogni file individualmente può adattarsi facilmente alla RAM), ignorando le directory vuote, in base alle idee delle altre soluzioni:

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()                          

Funziona così:

  1. Trova tutti i file nella directory in modo ricorsivo e ordinali per nome
  2. Calcola l'hash (predefinito: SHA-1) di ogni file (legge l'intero file in memoria)
  3. Crea un indice testuale con righe "filename = hash"
  4. Codifica di nuovo quell'indice in una stringa UTF-8 byte e hash

Puoi passare una funzione hash diversa come secondo parametro se SHA-1 non è la tua tazza di tè.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.