TL; DR:ファイルの内容のSHA-1をファイル名として使用して添付ファイル(不透明なファイル)を保存するCMSシステムがあります。 SHA-1ハッシュが両方のファイルに一致することをすでに知っている場合、アップロードされたファイルがストレージ内のファイルと本当に一致するかどうかを確認するにはどうすればよいですか?高性能が欲しいのですが。
ロングバージョン:
ユーザーが新しいファイルをシステムにアップロードするとき、アップロードされたファイルの内容のSHA-1ハッシュを計算し、同じハッシュのファイルがストレージバックエンドにすでに存在するかどうかを確認します。 PHPコードが実行される前に、アップロードされたファイルを/tmp
に配置し、アップロードされたファイルに対してsha1sum
を実行して、ファイルの内容のSHA-1ハッシュを取得します。次に、計算されたSHAからファンアウトを計算します。 -1ハッシュし、NFSマウントされたディレクトリ階層の下のストレージディレクトリを決定します(たとえば、ファイルの内容のSHA-1ハッシュが37aefc1e145992f2cc16fabadcfe23eede5fb094
の場合、永続的なファイル名は/nfs/data/files/37/ae/fc1e145992f2cc16fabadcfe23eede5fb094
です。)実際のファイルの内容を保存することに加えて、I INSERT
ユーザーが送信したメタデータ(例:Content-Type
、元のファイル名、日付スタンプなど)のSQLデータベースへの新しい行。
私が現在理解しているコーナーケースは、新しくアップロードされたファイルに、ストレージバックエンドの既存のハッシュと一致するSHA-1ハッシュがある場合です。偶然に起こったこの変化は天文学的に低いことを私は知っていますが、私は確信したいと思います。 (意図的な場合については、 https://shattered.io/ を参照してください)
2つのファイル名$file_a
と$file_b
が与えられた場合、両方のファイルの内容が同じかどうかをすばやく確認するにはどうすればよいですか?ファイルが大きすぎてメモリにロードできないと仮定します。 Pythonでは、filecmp.cmp()
を使用しますが、PHPは似たようなものがないようです。これは、fread()
と中止で実行できることを知っています。一致しないバイトが見つかったが、そのコードを記述したくない場合。
すでにSHA1の合計が1つある場合は、次のようにするだけです。
if ($known_sha1 == sha1_file($new_file))
さもないと
if (filesize($file_a) == filesize($file_b)
&& md5_file($file_a) == md5_file($file_b)
)
ファイルサイズもチェックして、ハッシュの衝突をある程度防止します(これはすでに非常にまれです)。また、MD5を使用しているのは、SHAアルゴリズムよりも大幅に高速であるためです(ただし、一意性は少し劣ります)。
更新:
これは、2つのファイルを互いに正確に比較する方法です。
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;
}
更新
ファイルが等しいことを確認したい場合は、最初にファイルサイズを確認し、それらが一致する場合は、ファイルの内容を比較する必要があります。これはハッシュ関数を使用するよりもはるかに高速であり、間違いなく正しい結果が得られます。
md5_file()
またはsha1_file()
または別のhash_functionを使用してコンテンツをハッシュする場合は、ファイルのコンテンツ全体をメモリにロードする必要はありません。 md5
を使用した例を次に示します。
$hash = md5_file('big.file'); // big.file is 1GB in my test
var_dump(memory_get_peak_usage());
出力:
int(330540)
あなたの例では、次のようになります。
if(md5_file('FILEA') === md5_file('FILEB')) {
echo 'files are equal';
}
さらに、ハッシュ関数を使用する場合、一方では複雑さ、他方では衝突の確率(2つの異なるメッセージが同じハッシュを生成することを意味する)のどちらかを決定する必要がある状況が常にあります。
あなたと同じように、Sha1ハッシュを使用します。それらが等しい場合は、md5ハッシュとファイルサイズも比較してください。次に、3つのチェックすべてで一致するが、等しくないファイルに遭遇した場合、聖杯を見つけただけです:D
ファイルが大きくてバイナリの場合、いくつかのオフセットから数バイトをテストできます。特に、関数が最初の異なる文字で結果を返す場合は、どのハッシュ関数よりもはるかに高速である必要があります。
ただし、この方法は、異なる文字が数個しかないファイルでは機能しません。大きなアーカイブやビデオなどに最適です。
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;
}