使用纯PHP验证两个文件是否相同?
TL;DR:我有一个CMS系统,它使用文件内容的SHA-1作为文件名存储附件(不透明文件)。考虑到我已经知道SHA-1哈希匹配两个文件,如何验证上传的文件是否真的匹配存储中的一个文件?我想要高性能的 长版本: 当用户将新文件上载到系统时,我计算上载文件内容的SHA-1散列,然后检查存储后端中是否已经存在具有相同散列的文件。PHP在我的代码运行之前将上传的文件放入使用纯PHP验证两个文件是否相同?,php,performance,file-upload,file-io,Php,Performance,File Upload,File Io,TL;DR:我有一个CMS系统,它使用文件内容的SHA-1作为文件名存储附件(不透明文件)。考虑到我已经知道SHA-1哈希匹配两个文件,如何验证上传的文件是否真的匹配存储中的一个文件?我想要高性能的 长版本: 当用户将新文件上载到系统时,我计算上载文件内容的SHA-1散列,然后检查存储后端中是否已经存在具有相同散列的文件。PHP在我的代码运行之前将上传的文件放入/tmp,然后我对上传的文件运行sha1sum,以获得文件内容的SHA-1哈希。然后,我从计算出的SHA-1散列计算扇出,并确定NFS装
/tmp
,然后我对上传的文件运行sha1sum
,以获得文件内容的SHA-1哈希。然后,我从计算出的SHA-1散列计算扇出,并确定NFS装载目录层次结构下的存储目录。(例如,如果文件内容的SHA-1散列为37aefc1e145992f2cc16fabadcfe23eede5fb094
则永久文件名为/nfs/data/files/37/ae/fc1e145992f2cc16fabadcfe23eede5fb094
)除了保存实际文件内容外,我还为用户提交的元数据在SQL数据库中插入一行(例如,内容类型
、原始文件名、日期戳等)
我目前正在考虑的一个极端情况是,新上传的文件具有与存储后端中现有哈希匹配的SHA-1哈希。我知道,意外发生的更改非常少,但我想确定一下。(有关故意的情况,请参阅)
给定两个文件名
$file\u a
和$file\u b
,如何快速检查两个文件是否具有相同的内容?假设文件太大,无法加载到内存中。对于Python,我会使用filecmp.cmp()
,但PHP似乎没有类似的功能。我知道这可以通过fread()实现
并在发现不匹配字节时中止,但我不想编写该代码。更新
如果要确保文件相等,则应首先检查文件大小,如果它们匹配,则只需区分文件内容。这比使用哈希函数快得多,并且肯定会给出正确的结果
如果使用
md5_file()
或sha1_file()
或其他哈希函数对内容进行哈希运算,则无需将整个文件内容加载到内存中。下面是使用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';
}
进一步注意,当您使用散列函数时,您总是需要在复杂性和冲突概率之间做出决定(这意味着两条不同的消息产生相同的散列)另一方面。像您一样使用Sha1哈希。如果它们相等,也比较它们的md5哈希和文件大小。
如果您随后遇到一个在所有3次检查中都匹配但不相等的文件-您刚刚找到了圣杯:D如果您已经有一个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;
}
当你的文件是大的二进制文件时,你可以从几个偏移量中测试几个字节。它应该比任何散列函数都要快,特别是该函数通过第一个不同的字符返回结果。
但是,这种方法不适用于只有几个不同字符的文件。它最适合于大型档案、视频等。
函数arefileequal($filename1,$filename2,$accurity)
{
$filesize1=文件大小($filename1);
$filesize2=文件大小($filename2);
如果($filesize1==$filesize2){
$file1=fopen($filename1,'r');
$file2=fopen($filename2,'r');
对于($i=0;$i),下面的代码帮助您检查文件是否相同
/***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";
所以我遇到了这个问题,然后找到了一个问题,这个问题回答了这个问题,而且确实有效
2021年…事情变了,所以我想我会发布一个链接到这个答案
A) 基本上,它使用了如上所示的fopen
和fread
,但它是有效的。对于我来说,被接受的答案总是不同的,即使是在同一个文件上
B) 如果您可以使用,那么fopen
和fread
方法将比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;
}
你是否试图避免散列冲突?使用散列是一个好主意。正如你所提到的,冲突的概率非常低-所以你可以确定在一般情况下,它是可以的。如果没有-让我们知道你对这些文件内容的情况:pgit正在使用sha1,所以我认为你使用sha1是足够安全的:)我正在尝试避免发生冲突bly因为散列冲突而丢失了文件内容。是的,如果我看到冲突,我会保留这两个文件。我敢打赌,在这种情况下,我会发现我的永久存储已被比特化。(在任何存储设备上获得随机位错误的变化似乎比查找SHA-1冲突要大得多;在这种情况下,我仍然希望获得损坏文件的新副本。)@Kakawait:git
在相信文件是相同的之前,也会进行字节比较测试,因为SHA-1哈希恰好匹配,据我所知。@hek2mgl:谢谢,我不知道PHP实现是否足够健全,无法将整个文件读入内存。我不需要使用shell_exec()
和sha1sum
处理大文件。是的,它们经常被忘记:)…还可以看看其他可能更快的散列函数。但是这些函数必须使用shell\u exec()
againI不会声称f
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;
}