使用CRC作为摘要来检测文件中的重复项

时间:2013-03-23 18:13:08

标签: crc crc32 crc64

CRC和类似计算(如Fletcher和Adler)的主要用途似乎是检测传输错误。因此,我所看到的大多数研究似乎都解决了检测两个数据集之间小规模差异的概率问题。我的需求略有不同。

以下是对该问题的非常近似的描述。细节要比这复杂得多,但下面的描述说明了我正在寻找的功能。这个小小的免责声明旨在解决诸如“#34;为什么你能以这种方式解决你的问题,而你可以通过我提出的其他方式更轻松地解决这个问题?" - 我需要以这种方式解决我的问题,因为这个问题与帖子没有密切关系,所以请不要发布这样的答案。

我正在处理分布式网络上的数据集(大小约1MB)。对这些数据集执行计算,速度/性能至关重要。我想要一种允许我避免重新传输数据集的机制。也就是说,我需要某种方法为给定大小的每个数据集生成唯一标识符(UID)。 (然后,我将数据集大小和UID从一台机器传输到另一台机器,并且接收机器只需要根据UID在本地没有数据的情况下请求传输数据。)

这类似于使用CRC检查更改到文件,并使用CRC作为摘要来检测文件中的重复之间的区别。我没有看到有关后者使用的任何讨论。

我不关心篡改问题,即我不需要加密强度哈希。

我目前正在使用序列化数据的简单32位CRC,到目前为止,这对我很有帮助。但是,我想知道是否有人可以推荐哪种32位CRC算法(即哪个多项式?)最适合在这种情况下最小化冲突的概率?

我的另一个问题是更微妙。在我当前的实现中,我忽略了我的数据集的结构,实际上只是CRC表示我的数据的序列化字符串。但是,出于各种原因,我想改变我的CRC方法如下。假设我的顶级数据集是一些原始数据和一些从属数据集的集合。我当前的方案基本上连接原始数据和所有从属数据集,然后CRC结果。但是,大多数时候我已经拥有了从属数据集的CRC,我宁愿通过将原始数据与 CRC 连接来构建顶层数据集的UID。从属数据集,然后CRC这个结构。问题是,使用这种方法如何影响碰撞的可能性?

用一种能让我讨论我的想法的语言,我会定义一些符号。调用我的顶级数据集T,并假设它由原始数据集R和下级数据集Si, i=1..n组成。我可以写为T = (R, S1, S2, ..., Sn)。如果&表示数据集的连接,我的原始方案可以被认为是:

UID_1(T) = CRC(R & S1 & S2 & ... & Sn)

我的新计划可以被认为是

UID_2(T) = CRC(R & CRC(S1) & CRC(S2) & ... & CRC(Sn))

然后我的问题是:(1)如果TT' 非常不同,那么什么CRC算法最小化prob( UID_1(T)=UID_1(T') ),哪种CRC算法最小化{ {1}},这两个概率如何比较?

关于此事的我(天真和不知情的)想法是这样的。假设prob( UID_2(T)=UID_2(T') )T之间的差异仅在一个从属数据集中,WLOG说T'。如果它发生S1!=S1',那么显然我们会CRC(S1)=CRC(S1')。另一方面,如果UID_2(T)=UID_2(T'),则CRC(S1)!=CRC(S1')R & CRC(S1) & CRC(S2) & ... & CRC(Sn)之间的差异仅为4个字节的差异,因此UID_2检测差异的能力实际上与CRC能够检测传输错误,即它能够仅在几个没有广泛分离的位中检测错误。由于这是CRC的设计目的,我认为UID_2非常安全,只要我使用的CRC擅长检测传输错误。用它来表示我们的符号,

R & CRC(S1') & CRC(S2) & ... & CRC(Sn)

让CRC调用未检测到几位prob( UID_2(T)=UID_2(T') ) = prob(CRC(S1)=CRC(S1')) + (1-prob(CRC(S1)=CRC(S1'))) * probability of CRC not detecting error a few bits. 的错误的概率,以及它没有检测到大尺寸数据集P上的大差异的概率。以上内容大致可以写成

Q

现在我将更改我的UID,如下所示。对于"基础"一段数据,即数据集prob( UID_2(T)=UID_2(T') ) ~ Q + (1-Q)*P ,其中R只是一个double,integer,char,bool等,定义T=(R)。然后,对于由下级数据集UID_3(T)=(R)向量组成的数据集T,定义

T = (S1, S2, ..., Sn)

假设某个特定数据集UID_3(T) = CRC(ID_3(S1) & ID_3(S2) & ... & ID_3(Sn)) 具有嵌套T的下级数据集 - 级别很深,那么,在一些模糊的意义上,我会认为

m

鉴于这些概率在任何情况下都很小,这可以近似为

prob( UID_3(T)=UID_3(T') ) ~ 1 - (1-Q)(1-P)^m

因此,如果我知道我的最大嵌套级别 1 - (1-Q)(1-P)^m = Q + (1-Q)*P*m + (1-Q)*P*P*m*(m-1)/2 + ... ~ Q + m*P ,并且我知道mP用于各种CRC,我想要的是选择能够为我提供最小值的CRC Q。如果我怀疑可能是Q + m*P,那么上述内容就简化了。我对UID_1的错误概率为P~Q。我对UID_3的错误概率是P,其中(m+1)P是我的最大嵌套(递归)级别。

这一切看起来都合理吗?

3 个答案:

答案 0 :(得分:4)

  

我想要一种允许我避免重新传输数据集的机制。

rsync已经使用您概述的方法解决了这个问题。

  

但是,我想知道是否有人可以推荐哪种32位CRC   算法(即哪个多项式?)最适合于最小化   在这种情况下碰撞的概率?

精心选择的CRC多项式之间不会有太大区别。速度对您来说可能更重要,在这种情况下,您可能需要使用硬件CRC,例如关于现代英特尔处理器的crc32指令。那个使用CRC-32C (Castagnoli) polynomial。通过在同一个循环中的三个缓冲区上计算CRC,然后将它们组合起来,通过并行使用单个核心上的所有三个算术单元,可以非常快速地实现这一点。请参阅下文如何组合CRC。

  

但是,大多数时候我已经拥有了下属的CRC   数据集,我宁愿构建我的顶级数据的UID   通过将原始数据与下属的CRC连接来设置   数据集,然后CRC这个结构。

或者你可以快速计算整个集合的CRC,就像你在整个事情上做了CRC一样,但是使用已经计算过的CRC的CRC。查看crc32_combine()中的zlib。这比采用一堆CRC的CRC要好。通过组合,您可以保留CRC算法的所有数学优势。

答案 1 :(得分:1)

Mark Adler的答案很响亮。如果我把我的程序员戴上帽子并戴上我的数学家的帽子,其中一些应该是显而易见的。他没有时间解释数学,所以我会在这里为那些感兴趣的人。

计算CRC的过程基本上是进行多项式除法的过程。多项式具有系数mod 2,即每个项的系数为0或1,因此N次多项式可以用N位数表示,每个位是一个项的系数(以及进行a的过程)多项式除法相当于进行了一大堆XOR和移位运算)。当对数据块进行CRC时,我们将“数据”视为一个大的多项式,即长的比特串,每个比特表示多项式中的项的系数。好吧,调用我们的数据块多项式A.对于每个CRC“版本”,已经为CRC选择了 多项式,我们将其称为P.对于32位CRC,P是多项式度数为32,所以它有33个项和33个系数。因为顶部系数总是1,所以它是隐式的,我们可以用32位整数表示32度多项式。 (计算上,这实际上非常方便。)计算数据块A的CRC的过程是当A除以P时找到余数的过程。也就是说,A总是可以写出

A = Q * P + R

其中R是小于P度的多项式,即R具有31或更小的度数,因此它可以用32位整数表示。 R基本上是CRC。 (小注意:通常一个前缀为0xFFFFFFFF到A,但这在这里并不重要。)现在,如果我们连接两个数据块A和B,对应于两个块的串联的“多项式”是A的多项式,“移位在左边“通过B中的比特数加上B.换句话说,A& B的多项式是A * S + B,其中S是对应于1的多项式,后跟N个零,其中N是B中的位数(即S = x ** N)。那么,对于A& B的CRC,我们可以说些什么呢?假设我们知道A = Q * P + R和B = Q'* P + R',即R是A的CRC,R'是B的CRC。假设我们也知道S = q * P + r。然后

A * S + B = (Q*P+R)*(q*P+r) + (Q'*P+R')
          = Q*(q*P+r)*P + R*q*P + R*r + Q'*P + R'
          = (Q*S + R*q + Q') * P    + R*r + R'

因此,为了找到当A * S + B除以P时的余数,我们只需要找到当R * r + R'除以P时的余数。因此,计算两个数据流的串联的CRC A和B,我们只需要知道数据流的单独CRC,即R和R',以及尾随数据流B​​的长度N(所以我们可以计算r)。这也是Marks其他评论之一的内容:如果尾随数据流B​​的长度被约束为几个值,我们可以为这些长度中的每一个预先计算r,使得两个CRC的组合非常简单。 (对于任意长度N,计算r并不是微不足道的,但它比在整个B上重新划分要快得多(log_2 N)。)

注意:上述内容并不是CRC的准确说明。还有一些转变。确切地说,如果L是由0xFFFFFFFF表示的多项式,即L = x * 31 + x * 30 + ... + x + 1,并且S_n是“向左移位n位”多项式,即S_n = x ** n,那么具有N位多项式A的数据块的CRC是当(L * S_N + A)* S_32除以P时的余数,即当(L& A)*时S_32除以P,其中&是“连接”运算符。

另外,我认为我不同意马克斯的评论之一,但如果我错了,他可以纠正我。如果我们已经知道R和R',则使用上述方法比较计算A& B的CRC的时间,与以直接方式计算它相比,不依赖于len(A)与len(B)的比率 - 以“直接”方式计算它,实际上不必在整个连接数据集上重新计算CRC。使用上面的符号,只需要计算R * S + B的CRC。也就是说,我们不是将预先挂起的0xFFFFFFFF转换为B并计算其CRC,而是将R预先加到B并计算其CRC。因此,将计算B的CRC的时间与计算r的时间进行比较(然后将R * r + R'除以P,这很可能是时间上无关紧要且无关紧要)。

答案 2 :(得分:0)

Mark Adler的answer解决了技术问题,因此不是我在这里做的事情。在这里,我将指出OP问题中提出的同步算法中的一个主要潜在缺陷,并提出一个小的改进。

校验和和散列为某些数据提供单一签名值。但是,如果数据较长,则具有有限长度,校验和/散列的可能唯一值的数量总是小于原始数据的可能组合。例如,4字节CRC只能采用4 294 967 296个唯一值,而即使是5字节值也可能是数据的8倍。这意味着对于任何长于校验和本身的数据,总会存在一个或多个具有完全相同签名的字节组合。

当用于检查完整性时,假设是导致相同签名的稍微不同的数据流的可能性很小,因此如果签名是,我们可以假设数据 相同相同。请务必注意,我们从一些数据d开始,并验证给定校验和c,使用校验和函数计算f f(d) == c

然而,在OP的算法中,不同的用途会引入一种微妙的,有害的信心退化。在OP的算法中,服务器A将以原始数据[d1A,d2A,d3A,d4A]开始并生成一组校验和[c1,c2,c3,c4](其中dnA是服务器A上的第n个数据项)。然后,服务器B将接收此校验和列表并检查其自己的校验和列表,以确定是否缺少任何校验和。假设服务器B具有列表[c1,c2,c3,c5]。然后应该发生的是它从服务器A请求d4并且在理想情况下同步已正常工作。

如果我们回想起碰撞的可能性,并且它并不总是需要那么多数据来产生碰撞(例如CRC("plumless") == CRC("buckeroo")),那么我们很快就会意识到我们的计划是最好的保证提供的是服务器B肯定没有d4A但它不能保证它有[d1A,d2A,d3A]。这是因为f(d1A) = c1f(d1B) = c1即使d1Ad1B有所不同,我们希望两台服务器同时拥有这两者。在这个方案中,服务器都不能知道d1Ad1B的存在。我们可以使用越来越多的抗冲突校验和散列,但这种方案永远不能保证完全同步。这变得更加重要,网络必须跟踪的文件数量越多。我建议使用像SHA1这样没有发现冲突的加密哈希值。

可能降低风险是引入冗余哈希值。一种方法是使用完全不同的算法,因为尽管可能crc32(d1) == crc32(d2) adler32(d1) == adler32(d2)同时发生crc32('a' & d1) == crc32('a' & d2)的可能性较小。这paper表示你不会以这种方式获得这么多。要使用OP表示法,crc32('b' & d1) == crc32('b' & d2)和{{1}}同时为真的不太可能,因此您可以"盐"减少易碰撞的组合。但是,我认为您也可以使用像SHA512这样的抗冲突哈希函数,实际上可能不会对您的性能产​​生很大的影响。