将 CRC 值保存在文件中,而不更改实际的 CRC 校验和?
Posted
技术标签:
【中文标题】将 CRC 值保存在文件中,而不更改实际的 CRC 校验和?【英文标题】:Save a CRC value in a file, without altering the actual CRC Checksum? 【发布时间】:2012-01-26 08:25:25 【问题描述】:我正在将一些我从自己的类中定义的对象保存到文件中。 (保存流数据)。
这很好,但我希望能够在文件中存储该文件的 CRC 校验和。
然后,每当我的应用程序尝试打开文件时,它都可以读取内部存储的 CRC 值。
然后对实际的文件进行检查,如果文件的CRC与内部存储的CRC值匹配我可以正常处理文件,否则显示错误消息说文件无效。
我需要一些关于如何做到这一点的建议,但我认为我可以这样做:
从我的应用程序中保存文件。 计算已保存文件的 CRC。 编辑保存 CRC 值的保存文件。 每当打开文件时,检查 CRC 是否与内部 CRC 值匹配。问题是,一旦文件中的单个数据字节被更改,就会导致 CRC 校验和完全不同 - 正如预期的那样。
【问题讨论】:
Warren 对你来说可能很明显,但我仍然在学习 Delphi 和一般编程。我大部分时间都在与逻辑和事情作斗争,通常是将自己与问题情况混淆。我想我现在会改变我的照片,这样你就不必再让我失望了。 @David 感谢您的支持信息 :) 抱歉,克雷格。对不起。 @Craig,您是否将 CRC32 仅用于错误检查而不是防止篡改? 感谢沃伦,如果我能更好地解决我心中的问题,我会做得更好,但我真的很难解决问题:(@Marcus我只是想要一种方法来验证文件是否有效并从我的应用程序中保存。所以我想检查这就是我想到 CRC 的原因。 【参考方案1】:我通常更喜欢将 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-hash 时很容易被屏蔽的地方,而且在第二个地方,在文件末尾附近具有可变偏移量。所以我决定使用 CRC 伪造,所以我得到了我的强哈希,而 zip 得到了它的有效 CRC32。
因为我已经为最终文件伪造了 CRC,所以我决定为原始头文件伪造它也不会受到伤害。因此,这种格式的所有文件现在都以具有 CRC 0xD1CE0DD5
的头文件开头。
【讨论】:
如果这还没有名字,我建议:“Ying/Yang CRC Embedding”。 +1(2 明天,现在没有票了)请注意,正因为如此,CRC 只能用于检测意外错误,而不是恶意更改文件。【参考方案2】:将 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;
如果您需要查找附加到文件的所有备用数据流(可能不止一个),您可以使用BackupRead 对它们进行iterate。 Internet Explorer 使用 ADS 支持“此文件已从 Internet 下载。您确定要打开它吗?”提示。
【讨论】:
【参考方案3】:我建议将校验和存储在另一个文件中,也许是 .ini 文件。或者对于一个非常奇怪的想法,您可以将校验和合并为文件名的一部分。 即 MyFile_checksum_digits_here.dat
【讨论】:
这不起作用,因为文件名在运行时没有设置,它是通过一个 TSaveDialog【参考方案4】:简单地说,您需要从校验和计算中排除用于存储校验和的字节。
将校验和写为文件中的最后一件事。除了校验和之外,根据文件的内容计算它。当您读取文件时,根据校验和之前的内容计算校验和。或者您可以将校验和写为随机访问的文件的第一个字节。只要你知道它在哪里。
【讨论】:
谢谢,现在看来很清楚了。我可以找到我保存在文件中的值并将其分配给一个变量,然后当我计算文件的 CRC(我的内部值的一部分除外)时,如果 CRC 与分配给我的变量的值匹配,那么我就知道了匹配。 @Craig 是的,可以的。如果程序在没有校验和的情况下仍然可以运行,那么 Craig Peterson 的 ADS 想法非常棒。如果您想要跨平台,那么它不太合适,因为它依赖于 NTFS。以上是关于将 CRC 值保存在文件中,而不更改实际的 CRC 校验和?的主要内容,如果未能解决你的问题,请参考以下文章