Verificare che due file siano identici usando puro PHP?

TL; DR: Ho un sistema CMS che memorizza gli allegati (file opachi) usando SHA-1 del contenuto del file come nome file. Come verificare se il file caricato corrisponde realmente a uno nella memory, dato che so già che l'hash SHA-1 corrisponde per entrambi i file? Mi piacerebbe avere performance elevate.

Versione lunga:

Quando un utente carica un nuovo file nel sistema, computo l'hash SHA-1 del contenuto del file caricato e poi controlla se esiste già un file con hash identico nel back-end di archiviazione. PHP mette il file caricato in /tmp prima che il mio codice possa essere eseguito e quindi sha1sum sul file caricato per get hash SHA-1 del contenuto del file. Quindi compongo fanout dall'hash SHA-1 calcolato e decido la directory di archiviazione nella gerarchia delle directory montate NFS. (Ad esempio, se l'hash SHA-1 per un contenuto di file è 37aefc1e145992f2cc16fabadcfe23eede5fb094 il nome file permanente è /nfs/data/files/37/ae/fc1e145992f2cc16fabadcfe23eede5fb094 .) Oltre a salvare il contenuto del file, INSERT una nuova row in un database SQL per i meta dati inviati dall'utente (ad es. Content-Type , nome file originale, datestamp, ecc.).

Il caso d'angolo che attualmente sto verificando è il caso in cui un nuovo file caricato ha un hash SHA-1 che corrisponde all'hash esistente nel back-end di storage. So che i cambiamenti che avvengono per incidente sono astronomicamente bassi, ma mi piacerebbe esserne sicuro. (Per caso specifico, consultare https://shattered.io/ )

Dati due nomi file $file_a e $file_b , come controllare rapidamente se entrambi i file hanno contenuti identici? Supponiamo che i file siano troppo grandi per essere caricati in memory. Con Python, filecmp.cmp() ma PHP non sembra avere nulla di simile. So che questo può essere fatto con fread() e abortire se viene trovato un byte non corrispondente, ma preferisco non scrivere quel codice.

Se hai già una sum SHA1, puoi semplicemente fare:

 if ($known_sha1 == sha1_file($new_file)) 

altrimenti

 if (filesize($file_a) == filesize($file_b) && md5_file($file_a) == md5_file($file_b) ) 

Controllando anche le size del file, per prevenire in qualche modo una collisione dell'hash (che è già molto improbabile). Anche usando MD5 perché è significativamente più veloce degli algoritmi SHA (ma un po 'less unico).


Aggiornare:

Ecco come confrontare esattamente due file uno contro l'altro.

 function compareFiles($file_a, $file_b) { if (filesize($file_a) == filesize($file_b)) { $fp_a = fopen($file_a, 'rb'); $fp_b = fopen($file_b, 'rb'); while (($b = fread($fp_a, 4096)) !== false) { $b_b = fread($fp_b, 4096); if ($b !== $b_b) { fclose($fp_a); fclose($fp_b); return false; } } fclose($fp_a); fclose($fp_b); return true; } return false; } 

Aggiornare

Se vuoi assicurarti che i file siano uguali, devi prima controllare le size dei file e, se corrispondono, basta diffare il contenuto del file. Questo è molto più veloce rispetto all'utilizzo di una function di hash e darà sicuramente il risultato corretto.


Non è necessario caricare l'integer contenuto del file in memory se si hash il contenuto utilizzando md5_file() o sha1_file() o un altro hash_function. Ecco un esempio usando md5 :

 $hash = md5_file('big.file'); // big.file is 1GB in my test var_dump(memory_get_peak_usage()); 

Produzione:

 int(330540) 

Nel tuo esempio sarebbe:

 if(md5_file('FILEA') === md5_file('FILEB')) { echo 'files are equal'; } 

Inoltre, quando si utilizza una function di hash, si avrà sempre una situazione in cui è necessario decidere tra la complessità da un lato e la probabilità di collisioni (nel senso che due diversi messaggi producono lo stesso hash) dall'altro.

Usa l'hash Sha1, proprio come fai tu. Se sono uguali, confronta anche i loro hash md5 e filesize. Se poi incontri un file che corrisponde a tutti e 3 i controlli, ma NON è uguale, hai appena trovato il Santo Graal: D

Quando i tuoi file sono grandi e binari, puoi testarne pochi byte da alcuni offset. Dovrebbe essere molto più veloce di qualsiasi function di hashing, specialmente se la function restituisce il risultato del primo carattere diverso.

Tuttavia, questo metodo non functionrà con file con pochi caratteri differenti. È il migliore per grandi archivi, video e così via.

 function areFilesEqual($filename1, $filename2, $accuracy) { $filesize1 = filesize($filename1); $filesize2 = filesize($filename2); if ($filesize1===$filesize2) { $file1 = fopen($filename1, 'r'); $file2 = fopen($filename2, 'r'); for ($i=0; $i<$filesize1 && $i<$filesize2; $i+=$accuracy) { fseek($file1, $i); fseek($file2, $i); if (fgetc($file1)!==fgetc($file2)) return false; } fclose($file1); fclose($file2); return true; } return false; } 

La seguente parte di codice ti aiuta a verificare se i file sono identici o less.

 /***check equality of files*/ $file1="pics/star.jpg"; $file2="pics/dupe.jpg"; if(sha1_file($file1)==sha1_file($file2)) echo "Identical"; else echo "Not Identical";