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()
完成,如果找到一个不匹配的字节则中止,但我不想写那个代码。
答案 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) 基本上它使用 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;
}
答案 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";