Calcola contemporaneamente più digest (md5, sha256)?


25

Partendo dal presupposto che l'I / O del disco e la RAM libera sono un collo di bottiglia (mentre il tempo della CPU non è la limitazione), esiste uno strumento in grado di calcolare più digest di messaggi contemporaneamente?

Sono particolarmente interessato a calcolare i digest MD-5 e SHA-256 di file di grandi dimensioni (dimensioni in gigabyte), preferibilmente in parallelo. Ho provato openssl dgst -sha256 -md5, ma calcola solo l'hash usando un algoritmo.

Pseudo-codice per il comportamento previsto:

for each block:
    for each algorithm:
        hash_state[algorithm].update(block)
for each algorithm:
    print algorithm, hash_state[algorithm].final_hash()

Puoi solo avviare un'istanza in background, quindi entrambi gli hash corrono in parallelo:for i in file1 file2 …; do sha256 "$i"& md5sum "$i"; done
Marco,

2
@Marco Il problema con questo approccio è che un comando potrebbe essere più veloce dell'altro, risultando in una cache del disco che viene svuotata e riempita in seguito con gli stessi dati.
Lekensteyn,

1
Se sei preoccupato per la cache del disco, puoi leggere il file solo una volta: for i in file1 file2 …; do tee < "$i" >(sha256sum) | md5sum ; doneQuindi devi aggiungere un codice aggiuntivo per contrassegnare il nome del file, perché viene inviato come input standard a md5sume sha256sum.
Marco,

Risposte:


28

Scopri pee(" tee standard input to pipes") da moreutils. Questo è sostanzialmente equivalente al teecomando di Marco , ma un po 'più semplice da digitare.

$ echo foo | pee md5sum sha256sum
d3b07384d113edec49eaa6238ad5ff00  -
b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c  -
$ pee md5sum sha256sum <foo.iso
f109ffd6612e36e0fc1597eda65e9cf0  -
469a38cb785f8d47a0f85f968feff0be1d6f9398e353496ff7aa9055725bc63e  -

Bel comando! Ho già installato questo pacchetto molto utile, non conoscevo questa utility dal nome divertente.
Lekensteyn,

1
peeha la migliore interfaccia, un confronto temporale con altri strumenti può essere trovato in questo post che dimostra anche uno strumento Python multi-thread.
Lekensteyn,

Sfortunatamente, è in moreutilsconflitto con GNU parallelil mio sistema Debian ... tuttavia, è bene sapere che esiste un tale strumento.
liori,

@Lekensteyn: ho un conflitto a livello di pacchetto (cioè aptitudenon mi permette di avere entrambi i pacchetti contemporaneamente).
liori,

@liori Peccato che Debian lo abbia implementato in quel modo, potrebbe valere la pena segnalare un bug su questo. Su Arch Linux c'è un moreutils-parallelnome per evitare il conflitto.
Lekensteyn,

10

È possibile utilizzare un forciclo per eseguire il ciclo sui singoli file e quindi utilizzare in tee combinazione con la sostituzione del processo (funziona in Bash e Zsh tra gli altri) per eseguire il pipe a diversi checksum.

Esempio:

for file in *.mkv; do
  tee < "$file" >(sha256sum) | md5sum
done

Puoi anche usare più di due checksum:

for file in *.mkv; do
  tee < "$file" >(sha256sum) >(sha384sum) | md5sum
done

Questo ha lo svantaggio che i checksum non conoscono il nome del file, perché viene passato come input standard. Se ciò non è accettabile, devi emettere manualmente i nomi dei file. Esempio completo:

for file in *.mkv; do
  echo "$file"
  tee < "$file" >(sha256sum) >(sha384sum) | md5sum
  echo
done > hashfilelist

1
Per rendere l'uscita compatibile con il *sumfamiglia di strumenti, questa espressione sed potrebbe essere utilizzato al posto: sed "s;-\$;${file//;/\\;};(ha sostituito il finale -dal nome del file, ma assicurarsi che il nome del file viene correttamente sfuggito).
Lekensteyn,

AFAICS, funziona solo in zsh. In ksh93 e bash, l'output di sha256sum va a md5sum. Avrai voglia: { tee < "$file" >(sha256sum >&3) | md5sum; } 3>&1. Vedere unix.stackexchange.com/q/153896/22565 per il problema inverso.
Stéphane Chazelas,

6

È un peccato che l'utilità openssl non accetti più comandi digest; Immagino che eseguire lo stesso comando su più file sia un modello di uso più comune. FWIW, la versione dell'utility openssl sul mio sistema (Mepis 11) ha solo comandi per sha e sha1, non per nessuna delle altre varianti sha. Ma ho un programma chiamato sha256sum e md5sum.

Ecco un semplice programma Python, dual_hash.py, che fa quello che vuoi. Una dimensione del blocco di 64k sembra essere ottimale per la mia macchina (Intel Pentium 4 2.00GHz con 2G di RAM), YMMV. Per file di piccole dimensioni, la sua velocità è all'incirca la stessa di eseguire md5sum e sha256sum in successione. Ma per file più grandi è significativamente più veloce. Ad esempio, su un file 1967063040 byte (un'immagine disco di una scheda SD piena di file mp3), md5sum + sha256sum richiede circa 1m44.9s, dual_hash.py richiede 1m0.312s.

dual_hash.py

#! /usr/bin/env python

''' Calculate MD5 and SHA-256 digests of a file simultaneously

    Written by PM 2Ring 2014.10.23
'''

import sys
import hashlib

def digests(fname, blocksize):
    md5 = hashlib.md5()
    sha = hashlib.sha256()
    with open(fname, 'rb') as f:
        while True:
            block = f.read(blocksize)
            if not block:
                break
            md5.update(block)
            sha.update(block)

    print("md5: %s" % md5.hexdigest())
    print("sha256: %s" % sha.hexdigest())

def main(*argv):
    blocksize = 1<<16 # 64kB
    if len(argv) < 2:
        print("No filename given!\n")
        print("Calculate md5 and sha-256 message digests of a file.")
        print("Usage:\npython %s filename [blocksize]\n" % sys.argv[0])
        print("Default blocksize=%d" % blocksize)
        return 1

    fname = argv[1]

    if len(argv) > 2:
        blocksize = int(sys.argv[2])

    print("Calculating MD5 and SHA-256 digests of %r using a blocksize of %d" % (fname, blocksize))
    digests(fname, blocksize)

if __name__ == '__main__':
    sys.exit(main(*sys.argv))

Suppongo che una versione C / C ++ di questo programma sarebbe un po 'più veloce, ma non molto, dal momento che gran parte del lavoro viene svolto dal modulo hashlib, che è scritto in C (o C ++). E come hai notato sopra, il collo di bottiglia per file di grandi dimensioni è la velocità IO.


Per un file di 2.3G, questa versione aveva una velocità comparabile rispetto md5sume sha256sumcombinata (4.7s + 14.2s vs 18.7s per questo script Python, file nella cache; 33.6s per il cold run). 64 KiB vs 1 MiB non hanno modificato la situazione. Con il codice commentato, 5.1s è stato speso per md5 (n = 3), 14.6s per sha1 (n = 3). Testato su un i5-460M con 8 GB di RAM. Immagino che questo potrebbe essere ulteriormente migliorato utilizzando più thread.
Lekensteyn,

C o C ++ probabilmente non importeranno la maggior parte del tempo di esecuzione nel modulo OpenSSL (utilizzato da hashlib). Più thread migliora la velocità, vedi questo post su uno script Python multi-thread .
Lekensteyn,

@PM 2Ring - Solo una nota. Dopo le istruzioni di stampa nella funzione digest (), è necessario cancellare almeno sha. Non posso dire se dovresti cancellare md5 o no. Vorrei solo usare "del sha". In caso contrario, ogni file dopo il primo avrà un hash errato. Per provarlo, crea una directory tmp e copia un file al suo interno. Ora crea 2 copie di quel file ed esegui il tuo script. Riceverai 3 hash diversi, che non è quello che vuoi. Modifica: ho pensato che la funzione stesse leggendo su una serie di file, non solo leggendo un singolo file alla volta ... Ignorare per questo uso. ;)
Terry Wendt,

1
@TerryWendt Mi hai fatto preoccupare lì per un secondo. :) Sì, digestselabora solo un singolo file per ogni chiamata. Quindi, anche se lo hai chiamato in un ciclo, creerà nuovi contesti md5 e sha per ogni chiamata. FWIW, potresti goderti il ​​mio hash SHA-256 ripristinabile .
PM 2Ring

5

Puoi sempre usare qualcosa come GNU parallel :

echo "/path/to/file" | parallel 'md5sum {} & sha256sum {}'

In alternativa, esegui uno dei due in background:

md5sum /path/to/file & sha256sum /path/to/file

Oppure, salvare l'output in file diversi ed eseguire più processi in background:

for file in *; do
    md5sum "$file" > "$file".md5 &
    sha256sum "$file" > "$file".sha &
done

Che lancerà il maggior numero md5sume sha256sumle istanze, come si dispone di file e saranno tutti in parallelo, salvando la loro produzione ai nomi dei file corrispondenti. Attenzione però, questo può diventare pesante se hai molti file.


1
Vedi il commento a Marco, la mia preoccupazione è che sebbene il comando sia parallelo, al disco lento si accede due volte per gli stessi dati.
Lekensteyn,

Ma l'esistenza della cache del disco non renderebbe inutili le tue preoccupazioni?
Twinkles,

2
@Twinkles Per citare Lekensteyn sopra, "Il problema con quell'approccio è che un comando potrebbe essere più veloce dell'altro, risultando in una cache del disco che viene svuotata e riempita successivamente con gli stessi dati".
Matt Nordhoff,

2
@MattNordhoff Ancora un'altra cosa che un programmatore I / O intelligente dovrebbe notare e ottimizzare. Si potrebbe pensare: "Quanto può essere difficile per uno scheduler I / O prendere in considerazione questo scenario?" Ma con scenari abbastanza diversi che uno scheduler I / O dovrebbe prendere in considerazione, diventa improvvisamente un problema difficile. Quindi sono d'accordo che non si dovrebbe presumere che la memorizzazione nella cache si occuperà del problema.
Kasperd,

1
Supponendo che l'IO sia significativamente più lenta di qualsiasi altro strumento coinvolto, entrambi gli strumenti dovrebbero essere rallentati alla stessa velocità a causa dell'IO. Pertanto, se uno strumento riesce a ottenere alcuni blocchi di dati in più rispetto all'altro, l'altro strumento potrebbe rapidamente recuperare il ritardo con i calcoli utilizzando i dati nella cache del disco. Questa è la teoria, mi piacerebbe vedere alcuni risultati sperimentali che lo dimostrano ...
liori,

3

Per curiosità se uno script multi-threaded Python ridurrebbe il tempo di esecuzione, ho creato questo digest.pyscript che usi threading.Thread, threading.Queueehashlib per calcolare gli hash per più file.

L'implementazione multi-thread di Python è in effetti leggermente più veloce rispetto all'uso peecon coreutils. Java d'altra parte è ... meh. I risultati sono disponibili in questo messaggio di commit :

Per confronto, per un file di 2,3 GiB (min / avg / max / sd sec per n = 10):

  • pee sha256sum md5sum <file: 16.5 / 16.9 /17.4/.305
  • python3 digest.py -sha256 -md5 <file: 13.7 / 15.0 /18.7/1.77
  • python2 digest.py -sha256 -md5 <file: 13.7 / 15.9 /18.7/1.64
  • jacksum -a sha256 + md5 -F '#CHECKSUM {i} #FILENAME': 32.7 / 37.1 /50/6.91

L'output hash è compatibile con l'output prodotto da coreutils. Poiché la lunghezza dipende dall'algoritmo di hashing, questo strumento non lo stampa. Utilizzo (per confronto, è peestato anche aggiunto):

$ ./digest.py -sha256 -md5 digest.py
c217e5aa3c3f9cfaca0d40b1060f6233297a3a0d2728dd19f1de3b28454975f2  digest.py
b575edf6387888a68c93bf89291f611c  digest.py
$ ./digest.py -sha256 -md5 <digest.py
c217e5aa3c3f9cfaca0d40b1060f6233297a3a0d2728dd19f1de3b28454975f2  -
b575edf6387888a68c93bf89291f611c  -
$ pee sha256sum md5sum <digest.py
c217e5aa3c3f9cfaca0d40b1060f6233297a3a0d2728dd19f1de3b28454975f2  -
b575edf6387888a68c93bf89291f611c  -

Stavo per suggerire un confronto pee "openssl sha256" "openssl md5" < file, ma, onestamente, l'ho appena provato e non ha battuto digest.py. Tuttavia, ha ridotto il divario.
Matt Nordhoff,

1

Jacksum è un'utilità gratuita e indipendente dalla piattaforma per il calcolo e la verifica di checksum, CRC e hash (digest dei messaggi) nonché timestamp di file. (estratto dalla pagina man di jacksum )

È in grado di riconoscere file di grandi dimensioni, è in grado di elaborare file di dimensioni fino a 8 Exabyte (= 8.000.000.000 di Gigabyte), presupponendo che il sistema operativo in uso sia compatibile anche con i file system. (estratto da http://www.jonelo.de/java/jacksum/ )

Esempio di utilizzo:

jacksum -a md5+sha256 -F "#ALGONAME{i} (#FILENAME) = #CHECKSUM{i}" jacksum-testfile

Uscita campione:

md5 (jacksum-testfile) = d41d8cd98f00b204e9800998ecf8427e
sha256 (jacksum-testfile) = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Su Ubuntu, esegui il comando apt-get install jacksum per ottenerlo.

In alternativa, i codici sorgente sono disponibili all'indirizzo


Mentre questo genera i checksum corretti, questo programma Java calcola due volte più lentamente dei coreutils. Vedi questo messaggio di commit .
Lekensteyn,
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.