19

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.) 実際のファイルの内容を保存することに加えてINSERT、ユーザーが送信したメタデータ (Content-Type元のファイル名など) の新しい行を SQL データベースに追加しました。 、日付スタンプなど)。

私が現在把握しているコーナー ケースは、新しくアップロードされたファイルに、ストレージ バックエンドの既存のハッシュと一致する SHA-1 ハッシュがある場合です。これが偶然に起こった場合の変化が天文学的に低いことは知っていますが、私は確信したいと思います. (意図的なケースについては、https://shattered.io/を参照してください)

$file_a2 つのファイル名とが与えられ$file_bた場合、両方のファイルの内容が同じかどうかをすばやく確認する方法は? ファイルが大きすぎてメモリにロードできないとします。Python の場合は使用しますfilecmp.cmp()が、PHP には似たようなものはないようです。一致しないバイトが見つかった場合、これを実行して中止できることは知っていますがfread()、そのコードを記述したくありません。

4

7 に答える 7

25

すでに SHA1 サムが 1 つある場合は、次のように簡単に実行できます。

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

それ以外は

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

ファイルサイズもチェックして、ハッシュの衝突をある程度防ぎます (これはすでに非常にありそうもないことです)。また、SHA アルゴリズムよりも大幅に高速であるため、MD5 を使用します (ただし、独自性は少し劣ります)。


アップデート:

これは、2 つのファイルを互いに正確に比較する方法です。

function compareFiles($file_a, $file_b)
{
    if (filesize($file_a) != filesize($file_b))
        return false;

    $chunksize = 4096;
    $fp_a = fopen($file_a, 'rb');
    $fp_b = fopen($file_b, 'rb');
        
    while (!feof($fp_a) && !feof($fp_b))
    {
        $d_a = fread($fp_a, $chunksize)
        $d_b = fread($fp_b, $chunksize);
        if ($d_a === false || $d_b === false || $d_a !== $d_b)
        {
            fclose($fp_a);
            fclose($fp_b);
            return false;
        }
    }
 
    fclose($fp_a);
    fclose($fp_b);
          
    return true;
}
于 2013-09-17T12:58:03.573 に答える
6

アップデート

ファイルが等しいことを確認したい場合は、最初にファイルサイズを確認し、一致する場合はファイルの内容を比較するだけです。これは、ハッシュ関数を使用するよりもはるかに高速であり、確実に正しい結果が得られます。


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 つの異なるメッセージが同じハッシュを生成することを意味します) の間で決定する必要がある状況が常に発生します。

于 2013-09-17T12:33:40.803 に答える
2

ファイルが大きくてバイナリの場合、いくつかのオフセットから数バイトをテストするだけです。特に関数が最初の異なる文字で結果を返すことは、どのハッシュ関数よりもはるかに高速である必要があります。

ただし、この方法は、異なる文字が数個しかないファイルでは機能しません。大きなアーカイブ、ビデオなどに最適です。

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;
}
于 2013-09-17T13:19:30.403 に答える
-1

次のコードは、ファイルが同一かどうかを確認するのに役立ちます。

/***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";
于 2013-10-28T09:53:40.373 に答える