使用纯PHP验证两个文件是否相同?

时间:2013-09-17 12:31:18

标签: php performance file-upload file-io

TL; DR:我有一个CMS系统,它使用文件内容的SHA-1作为文件名来存储附件(不透明文件)。如果我已经知道SHA-1哈希匹配两个文件,如何验证上传文件是否真的与存储中的文件匹配?我希望有很高的表现。

长版:

当用户将新文件上传到系统时,我计算上传文件内容的SHA-1哈希,然后检查存储后端中是否已存在具有相同哈希的文件。在我的代码运行之前,PHP将上传的文件放在/tmp中,然后针对上传的文件运行sha1sum以获取文件内容的SHA-1哈希值。然后,我从计算出的SHA-1哈希计算扇出,并确定NFS安装目录层次结构下的存储目录。 (例如,如果文件内容的SHA-1哈希为37aefc1e145992f2cc16fabadcfe23eede5fb094,则永久文件名为/nfs/data/files/37/ae/fc1e145992f2cc16fabadcfe23eede5fb094。)除了保存实际文件内容外,我INSERT新行到用户提交的元数据的SQL数据库(例如Content-Type,原始文件名,日期戳等)。

我目前正在搞清楚的是,新上传文件的SHA-1哈希值与存储后端中的现有哈希值相匹配。我知道事故发生的变化是天文数字低,但我想确定。 (有关案例,请参阅https://shattered.io/

鉴于两个文件名$file_a$file_b,如何快速检查两个文件是否具有相同的内容?假设文件太大而无法加载到内存中。使用Python,我会使用filecmp.cmp(),但PHP似乎没有任何类似的东西。我知道这可以用fread()完成,如果找到一个不匹配的字节则中止,但我不想写那个代码。

6 个答案:

答案 0 :(得分:16)

如果您已经有一个SHA1总和,则可以执行以下操作:

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

,否则

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

检查文件大小,以防止哈希冲突(已经非常不可能)。同样使用MD5,因为它比SHA算法快得多(但不那么独特)。


<强>更新

这是如何将两个文件完全相互比较的方式。

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;
}

答案 1 :(得分:4)

<强>更新

如果你想确保文件是相同的,那么你应该首先检查文件大小,如果它们匹配,那么只需区分文件内容。这比使用哈希函数要快得多,肯定能给出正确的结果。


如果使用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 :(得分:1)

使用Sha1哈希,就像你一样。如果它们相等,则还要比较它们的md5哈希值和文件大小。 如果你遇到一个在所有3个检查中都匹配的文件,但是不相等 - 你刚刚找到了圣杯:D

答案 3 :(得分:1)

当你的文件很大且是二进制文件时,你可以从几个偏移中测试它的几个字节。它应该比任何散列函数快得多,尤其是函数返回第一个不同字符的结果。

但是,此方法不适用于只有少数差异字符的文件。这是大型档案,视频等的最佳选择。

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;
}

答案 4 :(得分:0)

所以我遇到了这个问题,然后找到了一个可以回答它并且确实有效的问题。

2021 年...事情发生了变化,所以我想我会发布一个指向该答案的链接 Here

A) 基本上它使用 fopenfread,如上所示,但它有效。接受的答案总是对我来说不同,即使在同一个文件中也是如此。

B) 如果您可以使用 fopenfread 方法,它会比 sha1 或 md5 方法更快,但我不明白您为什么不能使用。

来自上面链接的 Svish 版本....

function files_are_equal($a, $b)
{
  // Check if filesize is different
  if(filesize($a) !== filesize($b))
      return false;

  // Check if content is different
  $ah = fopen($a, 'rb');
  $bh = fopen($b, 'rb');

  $result = true;
  while(!feof($ah))
  {
    if(fread($ah, 8192) != fread($bh, 8192))
    {
      $result = false;
      break;
    }
  }

  fclose($ah);
  fclose($bh);

  return $result;
}

答案 5 :(得分:-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";