这个校验和算法可以改进吗?
Posted
技术标签:
【中文标题】这个校验和算法可以改进吗?【英文标题】:Can this checksum algorithm be improved? 【发布时间】:2009-06-25 17:23:52 【问题描述】:我们有一个非常旧且不受支持的程序,它可以跨 SMB 共享复制文件。它有一个校验和算法来确定文件内容是否在复制之前发生了变化。该算法似乎很容易被愚弄——我们刚刚发现了一个示例,其中两个文件相同,除了单个“1”变为“2”外,返回相同的校验和。算法如下:
unsigned long GetFileCheckSum(CString PathFilename)
FILE* File;
unsigned long CheckSum = 0;
unsigned long Data = 0;
unsigned long Count = 0;
if ((File = fopen(PathFilename, "rb")) != NULL)
while (fread(&Data, 1, sizeof(unsigned long), File) != FALSE)
CheckSum ^= Data + ++Count;
Data = 0;
fclose(File);
return CheckSum;
我不是一个程序员(我是系统管理员),但我知道基于 XOR 的校验和会非常粗糙。对于两个大小相同但内容不同的文件,该算法返回相同校验和的可能性有多大? (我不期待一个确切的答案,“远程”或“很可能”都可以。)
如何在不影响性能的情况下对其进行改进?
最后,fread()
是怎么回事?我快速浏览了文档,但无法弄清楚。 Data
是否被依次设置为文件的每个字节? 编辑:好的,所以它正在将文件读入unsigned long
(我们假设这里是 32 位操作系统)块。每个块包含什么?如果文件内容是abcd
,那么第一遍Data
的值是多少?是不是(在 Perl 中):
(ord('a') << 24) & (ord('b') << 16) & (ord('c') << 8) & ord('d')
【问题讨论】:
fread 正在读取一个元素到数据的地址。正在读取的元素的大小是 unsigned long 的大小(我认为是 8 个字节)。 unsigned long 的大小取决于处理器架构(32/64 位)等因素,这就是使用 sizeof 的原因。 好吧,假设一个大端系统是正确的。在 little-endian 系统上,情况正好相反。 【参考方案1】:MD5 常用于验证传输文件的完整性。源代码在 c++ 中很容易获得。它被广泛认为是一种快速准确的算法。
另见Robust and fast checksum algorithm?
【讨论】:
旁注:MD5 仅适用于验证来自受信任来源的文件完整性。可以创建具有相同 MD5 校验和的两个文件,前提是提前完成并且同时创建两个文件。但是,制作一个与另一个具有相同 MD5 的文件非常困难。 如果你不关心加密强度,MD4 比 MD5 更简单、更快,而 CRC-32 明显更简单、更快。但是,它们中没有一个会等于 OP(损坏的)“校验和”的速度。 我认为 MD4 和 CRC32 将等同于速度,因为它们都可能受 I/O 限制。对于现代 CPU,甚至 MD5 也可能是 I/O 绑定的。 啊,OP 进行 4 字节读取,这几乎肯定会受到 I/O 限制。好吧,如果你只在记忆中工作,我认为我的排名仍然有效。【参考方案2】:我建议你看看Fletcher's checksum,特别是 fletcher-32,它应该相当快,并且可以检测到当前 XOR 链无法检测到的各种东西。
【讨论】:
【参考方案3】:您可以使用如下公式轻松改进算法:
Checksum = (Checksum * a + Data * b) + c;
如果 a、b 和 c 是大素数,这应该会返回好的结果。在此之后,旋转(而不是移动!)校验和的位将进一步改善它。
使用素数,这是一种类似于 Linear congruential generators 的算法 - 它保证了长周期和良好的分布。
【讨论】:
我不确定这对分发有何帮助!它有助于强化恶意攻击。 假设文件是很多 ASCII 文本,这将确保您不会总是对大约 5 个字节的方差进行异或运算,并且会通过校验和分散熵。【参考方案4】:fread
位一次读取文件中的一个块。每个块都是 long 的大小(在 c 中这不是一个明确定义的大小,但您可以假设 32 或 64 位)。根据缓冲的方式,这可能还不错。 OTOH,将更大的块读入数组并循环它可能会快得多。
【讨论】:
【参考方案5】:即使是“昂贵”的加密哈希函数通常也需要多次迭代才能花费大量时间。虽然不再推荐用于加密目的(用户会故意尝试创建冲突),但 SHA1 和 MD5 等函数已广泛可用且适用于此目的。
如果需要较小的哈希值,CRC 是可以的,但不是很好。 n 位 CRC 将无法检测到比 n 位长的一小部分更改。例如,假设文件中只有一个美元金额发生了变化,从 12,345 美元变为 34,567 美元。 32 位 CRC 可能会错过该更改。
截断较长加密哈希的结果将比 CRC 更可靠地检测更改。
【讨论】:
【参考方案6】:我似乎您的算法没有努力处理大小不是 4 字节的精确倍数的文件。 fread 的返回值不是布尔值,而是实际读取的字节数,在 EOF 或发生错误的情况下与 4 不同。你没有被检查,只是假设如果它没有返回 0,你在“数据”中有 4 个有效字节来计算你的哈希。
如果您真的想使用哈希,我会推荐几件事。首先,使用像 MD5 这样的简单加密哈希,而不是 CRC32。 CRC32 在检查数据有效性方面是不错的,但对于跨越文件系统并确保没有冲突,它不是一个很好的工具,因为在其他地方的 cmets 中提到了生日悖论。其次,不要自己写函数。查找现有的实现。最后,考虑简单地使用 rsync 来复制文件,而不是滚动您自己的解决方案。
【讨论】:
我认为(假设没有错误并且文件长度 > sizeof(long))哈希将返回一致的结果,因为最后一次读取的最后一位将始终保持在最后一次迭代中。跨度> 这依赖于代码中的两个缺陷来确保正确的功能。如果有人在每个循环之间添加代码将“数据”重置为 0,它也会产生一致的结果,但现在任何以前存储的 CRC 值都是不正确的。 其实,马丁删除的答案是在正确的轨道上。在此应用程序中,您尝试检测两个特定文件是否相同,而不是一个文件是否与集合中的任何文件匹配。所以,生日问题不适用。 文件系统中文件之间的冲突并不重要——我们只需要知道“文件 x 是否已更改?”如果是,复制它,如果不是,不要。由于各种原因,我们不能使用 rsync。 'Can't use rsync for various reasons'有点奇怪,因为它支持几乎所有还支持 samba 或 Windows 共享的平台,但没关系。我对 fread() 的实现和使用的评论立场。【参考方案7】:
CheckSum ^= Data + ++Count;
Data = 0;
我不认为“++Count”做很多工作。代码等价于
CheckSum ^= Data;
对一个字节序列进行异或是不够的。尤其是文本文件。 我建议使用hash function。
【讨论】:
好吧,++Count 做了很多工作,例如它可以防止像 chk(ABCD) = chk(DCBA) 这样的琐碎冲突,当使用 ^= Data (A, B, C, D are意味着在这里是无符号长的) 好的,但是注意它只影响每轮的第 4 个字节,每 256 个循环的第 3 个字节,每 65536 个的第 2 个字节,等等。【参考方案8】:SHA-1 和(最近的 SHA-2)提供了出色的散列函数,我相信由于更好的散列特性,它们正在慢慢取代 MD5。所有这些(md2、sha 等)都有高效的实现,并返回几个字符长的缓冲区的哈希值(尽管始终是固定长度)。被证明比将散列简化为整数更可靠。如果我有我的 druthers,我会使用 SHA-2。关注 this link 了解实现 SHA 校验和的库。
如果您不想在这些库中编译,linux(可能还有 cygwin)具有以下可执行文件:md5sum、sha1sum、sha224sum、sha256sum、sha384sum、sha512sum;您可以向其中提供您的文件,他们会将校验和打印为十六进制字符串。 你可以使用 popen 来执行这些程序——用这样的东西:
const int maxBuf=1024;
char buf[maxBuf];
FILE* f = popen( "sha224sum myfile", "w" );
int bytesRead = f.read( buf, maxBuf );
fclose( f );
显然,这会运行得慢很多,但对于第一次通过来说是有用的。 如果速度是一个问题,考虑到像这样的文件散列操作和 I/O 绑定(内存和磁盘访问将成为瓶颈),我希望所有这些算法的运行速度与产生无符号整数的算法一样快。 Perl 和 Python 还附带了 MD5 SHA1 和 SHA2 的实现,并且可能会像在 C/C++ 中一样快。
【讨论】:
SHA-1 具有比 MD-5 更好的加密属性。这在这里无关紧要。 也许吧。取决于应用程序。这里讨论了 MD5 的(加密)问题。 en.wikipedia.org/wiki/MD5(它还说 SHA1 出于加密目的而被破坏,顺便说一句。)以上是关于这个校验和算法可以改进吗?的主要内容,如果未能解决你的问题,请参考以下文章