我将从我自己的类中定义的一些对象保存到File。 (保存流数据)。
这一切都很好,但我希望能够在文件中存储该文件的CRC校验和。
然后,每当我的应用程序尝试打开文件时,它都可以读取内部存储的CRC值。
然后检查实际文件,如果文件的CRC与内部存储的CRC值匹配,我可以正常处理文件,否则显示错误消息,说文件无效。
我需要一些关于如何做到这一点的建议,我想我可以这样做:
问题是,只要在文件中更改了单个数据字节,就会导致CRC校验和完全不同 - 正如预期的那样。
答案 0 :(得分:10)
我通常更喜欢CRC从检查中排除的方法。但如果出于某种原因这是不可能的,那就有一种解决方法:
您需要保留8个字节,4个用于CRC,4个用于补偿数据。首先使用某个虚拟值填充保留字节(比如说0x00
)。然后将CRC计算为前4个字节,最后更改其他4个字节,使文件的CRC保持不变。
有关如何执行此计算的详细信息:Reversing CRC32
我实际上在one of my projects中使用了它:
我正在设计一种基于zip的文件格式。存档中的第一个文件以未压缩的形式存储,并用作头文件。这也意味着它存储在文件中的固定偏移处。到目前为止非常标准,类似于例如ePub。
现在我决定在标题中包含一个sha1哈希,为每个文件提供基于Id的唯一内容和完整性检查。由于标头以及sha1散列位于文件中的已知偏移处,因此在散列时屏蔽它是微不足道的。所以我放入一个虚拟哈希并创建zip文件,然后散列文件并填入真正的哈希值。
但是现在有一个问题:Zip存储所有包含文件的CRC。而且不仅在sha1-hashing时容易屏蔽的一个地方,而且在文件末尾附近有可变偏移的第二个地方。所以我决定使用CRC伪装,所以我得到了强大的哈希值,zip获得了有效的CRC32。
由于我已经伪造了最终文件的CRC,我决定伪造原始头文件也不会受到影响。因此,此格式的所有文件现在都以包含CRC 0xD1CE0DD5
的头文件开头。
答案 1 :(得分:8)
简单地说,您需要从校验和计算中排除用于存储校验和的字节。
将校验和写为文件中的最后一项。除校验和外,根据文件内容计算它。当您来读取文件时,根据校验和之前的内容计算校验和。或者您可以将校验和写为具有随机访问权限的文件的第一个字节。只要你知道它在哪里。
答案 2 :(得分:6)
将CRC存储为文件本身的一部分,但不包括CRC计算中的数据。如果在将CRC字段传递给CRC函数之前,有某种固定的标头将CRC字段归零。如果没有,只需将它附加到文件的末尾,并将除最后4个字节之外的所有内容传递给CRC函数。
或者,如果文件存储在NTFS驱动器上而您不需要将它们传输到另一台计算机,则可以使用NTFS Alternate Data Streams来存储CRC。基本上,您打开文件,ADS名称用冒号(如C:\file.txt:CRC
)与文件名分隔。 Windows在内部处理差异,因此您可以使用普通的TFileStream函数来操作它们。
备用数据流与标准文件流分开存储,因此仅打开或修改C:\file.txt
不会影响它。
所以,代码看起来像这样:
procedure UpdateCRC(const aFileName: string);
var
FileStream, ADSStream: TStream;
CRC: LongWord;
begin
FileStream := TFileStream.Create(aFileName, fmOpenRead);
try
CRC := CrcOf(FileStream);
finally
FileStream.Free;
end;
ADSStream := TFileStream.Create(aFileName + ':CRC', fmCreate);
try
ADSStream.WriteBuffer(CRC, SizeOf(CRC));
finally
ADSStream.Free;
end;
end;
如果您需要查找附加到文件的所有备用数据流(可以有多个),您可以使用iterate BackupRead覆盖它们。 Internet Explorer使用ADS支持“此文件已从Internet下载。您确定要打开它吗?”提示。
答案 3 :(得分:1)
我建议将校验和存储在另一个文件中,也可以是.ini文件。或者对于一个非常奇怪的想法,您可以将校验和作为文件名的一部分 即MyFile_checksum_digits_here.dat