19

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()并中止,但我宁愿不编写该代码。

4

7 回答 7

25

如果您已经有一个 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))
        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';
}

进一步注意,当您使用散列函数时,您总是会遇到这样一种情况,一方面您需要在复杂性和冲突概率(意味着两条不同的消息产生相同的散列)之间做出决定。

于 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

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

于 2013-09-17T12:51:26.093 回答
0

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

2021 年……事情发生了变化,所以我想我会在此处发布指向该答案的链接

A)基本上它使用fopenfread如上所示,但它有效。即使在同一个文件上,接受的答案总是对我返回不同的结果。

B)fopenandfread方法将比 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;
}
于 2021-06-11T07:32:54.570 回答
0

您可以使用 turbodepot 库。它是纯 PHP 的,只需一行代码即可解决此问题:

require 'path/to/your/dependencies/folder/turbocommons-php-X.X.X.phar';
require 'path/to/your/dependencies/folder/turbodepot-php-X.X.X.phar';

use org\turbodepot\src\main\php\managers\FilesManager;

$filesManager = new FilesManager();
$filesManager->isFileEqualTo('path/to/file1', 'path/to/file2');

您可以在此处查看代码,它基本上首先按大小进行比较,然后按数据块进行比较:

https://github.com/edertone/TurboDepot/blob/f74a12ac330ec49604403a2f60502ced591c6da8/TurboDepot-Php/src/main/php/managers/FilesManager.php#L129

通过使用这个库,您还可以获得大量文件系统功能,例如比较两个文件夹、搜索文件夹、镜像文件夹等等

更多信息在这里:

https://turboframework.org/en/blog/2020-11-03/check-if-two-files-are-identical-using-javascript-typescript-php

于 2021-12-24T06:56:28.467 回答
-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 回答