不要跑,CRC没这么难!(简单易懂的CRC原理阐述)

卵石之美

不要跑,CRC没这么难!(简单易懂的CRC原理阐述)


网上大多的教材都是面向大佬的很多细节原理都没有讲清楚,对于我们这些新萌菜鸟们实在太不友好了。于是我写一篇相对轻松易懂的博客,希望能对学习CRC的朋友们有所帮助吧!

什么是CRC???


你说小学3年级的小明同学不知好歹喜欢村长女儿王大麻子,于是羞涩的他想到写一封情书已表心意。正所谓闺女似情人,爱女心切的村长凡是信件统统要过他之手。如果这份情书被她爸稍加“几笔”岂不悲剧了?

奇偶验证

如何验证情书是否被动过手脚(验证数据是否损坏),介于王大麻子数学不行,数数还行。作为数学课代表的小明同学立刻想到一个好主意:将所有的文字转化二进制,只要数一数1的数量是奇数还是偶数不就可以了吗!

比如我要传递M这个字符那么他的ASCII的值为0100 1101(M),就是数据末尾加上一个1或0,使其整个数据的1的数量可以凑齐奇数或偶数。

如果规定信件所有1的个数为奇数的话,那么0100 1101(M)才4个1,我们就数据的末尾加上一个1凑齐奇数(5个1):0100 1101 1;如果数据中的1刚好奇数,我们就在末尾加上一个0就可(这个方法叫做奇校验),当然相反如果规定的信件所有1的个数为偶数(偶校验),刚好处理的方法也是相反。

虽然这个方法简单到连他没有上学的弟弟都做得起(很容易通过硬件方式实现验证),但是这个的出错率是五五开啊(刚好少/多偶数个1),且永远检查不出0的错误(就算多100个零也看不出来)。

累加和校验

你说奇偶不行,哪我相加总该行吧?于是乎小明同学又推出第二号方案:将每一个文字(字节)的值相加来验证。

比如传递LOVE,我们就可以得到四个数字76(L) 79(O) 86(V) 69(E),他们相加可得310,如果这个数字嫌他太大了不好写,我们可以限制他在0~255(一个字节)这个范围之内,那么我们就得到一个验证数字55 =310 – 255(最简单的办法就是减去255的倍数)。然后将数字附加数据后面,对方接受到数据执行同样的操作对比验证数字是否一致。

虽说这个方法要比上面的方法要可靠一些,但凡事总有列外:

一、比如数据错误刚好等于验证数字
我们要传输的数据:76 79 86 69 310(验证数字)
发生错误的数据: 76 78(-1) 87(+1) 69 310(验证数字还是一样的)

二、单个字符的出错率为1/256

CRC验证原理

无数日夜小明同学冥思苦想,最终还剩最后三根头发之际他学会除法,头顶一凉想到一个绝佳主意:如果我将信件上的所有文字组合成一个长数字,然后用一个我和王大麻子都知道的数字(约定的多项式)去除以,将余数作为一个验证数字的话……

假设我要传输一个字符87(W),和王大麻子约定除数为6,那么结果很明显就是87 ÷ 6 = 14……3,那么我们可以将3作为验证数字附加原始数据的末尾发送。

但明显我们常规的借位除法大大超出了王大麻子的数学水平,于是小明同学决定不做人了发明一个二进制除法,并取名为“模二除法”。

所谓模二除法实际形式上和我们的平常使用的除法一样的(用于二进制运算),但是唯一不一同的是关于计算当中一切的加减法统统换成“异或”(XOR),一句话概括异或运算就是:异性相吸(返回真1),同性相斥(返回假0):1 Xor 0 = 1,0 Xor 1 = 1, 1 Xor 1 = 0, 0 Xor 0 = 0。

那么我们用上面列子来试一下模二除法(模二除法的结果不等于普通除法)

图片描述

王大麻子表示:我去!算完之后我还要核对余数?!不能再简单点吗?

于是乎小明同学又开始没日没夜苦想,最终当最后的狼牙山“三壮士”也离他而去时,他头顶一凉想到一个绝佳主意:在信件数据的末尾(即数据低位LSB)补上我和王大麻子都知道的数字的长度-1的0(生成项长度-1)然后在被同知数字相除,得到的余数再附加原始数据的末尾上。(这里说的原始数据是指没有补零的,且余数长度一定与补零的长度一致)

口说无凭,我们来重新用这个升级计算方法试一试。

图片描述

我们将余数10补在原始数据1010111的末尾得到了一个新数:101011110,然后我们再用110去除,神奇的事情发生了:没有余数了。(接受端只要将已修改的数据与生成项模二相除没有余数即代表数据无误)

这便是CRC(循环冗余校验,Cyclic Redundancy Check)是一种为了检测数据是否损坏处理办法。而当中的模二除法是无借位(不向高位借位的)的性质十分重要:意味我们计算CRC只需要简单的数据位移和Xor即可(到后面你就知道了)。

当然理论上讲CRC还是出错的可能(不过已经比程序猿能找到女朋友的几率要低了),所以选择一个好的除数就是一个非常重要的事情,关于CRC的除数有个专业的名称:生成多项式,简称生成项。如何选择生成项这是需要一定的代数编码知识(已经不是我这种咸鱼能搞懂的问题了)。好在我们可以直接用大佬计算好的生成项,不同的生成项有不同的CRC,如:CRC8、CRC16、CRC-CCITT、CRC32……。

从数学的角度来理解CRC(如果您只是了解如何计算CRC的话可以直接跳过本节)

我们不妨尝试将二进制转化一种多项式:数字中多少位1代表的是x的多少次方,0乘以任何数都是0所以不用管。

比如101010(42),可以转化为:x^5+x^3+x。(注意最低位0次方,任何数零次方都等于1)

假设用x^3+x^2+x除去x+1,可以得到如下式子:(这一段基本上完全搬运的循環冗餘校驗Wiki,写的真的好!)

图片描述

很好我们在两边再乘一个x+1:

图片描述

这里不难看出:得到一个商×除数+余数的式子,其中的(x^2+1)就是商,-1就是余数(我也不没懂为啥余数是负数)。我们还可以对左边式子再提取一次x,可得:

图片描述

我们可以理解这里提取的x是对于x^2+x+1(1011)进行补一次零变成了10110,实际上这个补零还有个说法叫做左移(要注意数据的方向高位在左边)。

如何理解补零的性质这个很简单,我们类比十进制的补零如:1要补两次零变成100,很明显补零就是乘与10的几次方。回到二进制中就是2的几次方,而多项式中的x就可以代表2。

通过以上的式子的整理可以得出通用公式即是:

图片描述

M(x)就代表的是原始数据,x^n代表的是数据末补n个0,K(x)代表的是Key也就是生成项,R(x)代表的余数也是上一节提到FCS。接收者接受到M(x)*x^n+R(x)看看是能被K(x)整除不,可以即为正确数据。

要注意的一点x^n的长度(补零长度)受限于R(x)(目的是可以直接追加在末尾,而不影响原始数据):他们长度一致,且一定比K(x)的长度少1。

关于R(x)为什么一定比K(x)的长度少1?我个人愚见是特殊的模二除法:K(x)的最高位一定1(不然没有意义啊),而数据处理过程中需要计算数据的最高位也是1(可以对照着除法的当中与除数对应的那个被除数的那部分数据),他们进行Xor就变成0,实际计算往往是剩下的部分(K(x)长度-1)(在程序设计中反正都会变成0干脆都不计算首位,这就是为啥网上的CRC生成多项式的简记码都是默认舍弃首位1的原因)。

CRC的原理实际上远比这些要复杂的多,涉及到群论这类我们这些吃瓜群众可望不可即的数理知识。以上也只是我对Wiki的搬运和理解瞎猜,希望大家能通过这个简单列子大概了解CRC的性质,如有不对之处有望大佬不惜赐教!

直接计算法


虽说上一节我们已经知道CRC是怎么计算的,但明显电脑可不会写除法公式(程序很难直接用这种方法)。且不说要对齐一个超长二进制数据进行逐位计算的困难不说,单单是像vbscript这种既没有几个位操作符又用起来蛋疼的语言基本上是很难实现(大佬:喵喵喵?)。不过可以转化一个思路:比如每次只取一部分数据来计算如何?

仔细观察计算的过程,可以发现其实每一次Xor都是固定不动的生成项与其对应的数据首位“消1”。那我们就可以假想出一个与生成项长度一致的“盒子”,取出一部分的数据出来若首位是1时就进行一次Xor,遇到0则左移到1为止,左移造成的右端的空缺用0补充。而这里0希望理解为一种“存储”,它“存储” 生成项中未和数据进行计算的那一部分,按顺序先后附加被计算数据的后面,当先一部分的数据全部计算之后,实际上“盒子”中剩下都是未和数据计算的部分的“和”11011 xor 10110 = 11011 xor ( 10000 xor 00100 xor 00010)(这里实际上就是Xor的交换律到后面就会体会到他的强大)

图片描述

通过以上的结论我们可以假装设计出一个算法(这里我用的是VBScript):

Const PLAY = &H1021&      '这里生成项(实际上就是CRC-CCITT),注意这里默认首位1是去掉的
Const TEXT = "123456789"  '这里就是原始数据

Dim L,I,CRC
Do While L < Len(TEXT)
'通过循环取得原始数据的每一个字符来计算
    L = L + 1
    CRC = CRC Xor (Asc(Mid(TEXT,L,1)) * &H100&)
    '实际上文中提到的“盒子”专业的说法应该叫做:寄存器。
    '这里取出新的字符出来与CRC寄存器里上一次未和数据进行计算的多项式的部分进行Xor,作为新数据进行下一步处理。
    '机智的你也许发现了:1021这个生成式是16位与一个字节8位不符怎么办?别忘我们还有神器——补零!乘上H100 = 256 = 2 ^ 8
    For I = 0 To 7
        '判断数据最高位是否为 1
        '1 - 左移一位(去掉数据首位1),剩下的部分进行Xor
        '0 - 就左移一位(去掉多余的0)
        If (CRC And &H8000&) Then 
            CRC = (CRC * 2) And &HFFFF& '// 左移
            CRC = CRC Xor PLAY
        Else
            CRC = (CRC * 2) And &HFFFF& '// 左移
        End If
    Next
    CRC = CRC And &HFFFF&
    '限制寄存器内数据大小为16位。
Loop

WScript.Echo Hex(CRC)

运行之后成功就可以得出CRC-CCITT (XModem)的标准验证值:31C3(在线计算网站:On-line CRC calculation and free library

驱动表法


实际上CRC就像开心消消乐一样,就是不断消除首位数据。这时你想:要是能一口气消除一个字节(8bit)以上的数据那该多好!

一般来讲文艺青年会这么做(CRC的正常计算):

图片描述

但是2B青年会这么想:可不可以将生成项先Xor了?

图片描述

我们可以先提前计算出与数据前几位相符的生成项之和再Xor从而到达一口气消掉了多位数据的目的,Xor的乘法交换律允许我们提前计算出需要的数据A Xor B Xor C = A Xor ( B Xor C )

既然如此干脆直接将前八位0000 0000 ~ 1111 1111(0 ~ 255,一个字节)这个范围所有的生产项的和全部计算存储成表格,等计算的时候直接取出数据的首字节出来作为索引找到对应表格的中生存项的和与去掉首位字节的数据进行Xor不就可以了吗。

表的由来

虽说想法很美好,但是如何实现前8位从0~255所有的生成项之和了?我们来思考一下CRC计算的本质是什么?复读没错是不断地消除首位数据,那么在Xor运算下消除方法就是:数据一样!

那么我们将0~255这256个数字进行CRC逐位计算后剩下不就是已经消除掉前8位数据的生成项之和吗!(因为前8位数据一样所以被消除了)

用通俗点语言就是:我们提前将一个字节的CRC验证码计算出来。(实际上这一段语文老师死得早的我构思很久,担心没有上面这段过渡部分会造成读者理解困难,希望大家能Get我的点……Orz)

以下就是关于CRC16的正序表格计算代码:

Const PLAY = &H8005&     '这里生成项(CRC16),注意这里默认首位1是去掉的
ReDim Table(255)         '这里存储表格

'// 计算表格部分
Dim I,J,Temp
'对于0~255这256个数字进行CRC计算,并将计算好的CRC验证码按顺序存储在表格(数组)中
For I = 0 To 255
    Temp = (I * &H100&) And &HFFFF&
    For J = 0 To 7
        If (Temp And &H8000) Then
            Temp = (Temp * 2) And &HFFFF&
            Temp = Temp Xor PLAY
        Else
            Temp = (Temp * 2) And &HFFFF&
        End If
    Next
    Table(I) = Temp And &HFFFF&
Next

'// 输出CRC16表格代码(使用VbsEdit的调试)
Dim y,x,u
Debug.WriteLine "Dim CRC16_Table:CRC16_Table = Array( _"
For y = 1 To 64
    For x = 1 To 4
        Debug.Write "&H",String(4 - Len(Hex(Table(u))),"0"),Hex(Table(u)),"&"
        If u <> 255 Then Debug.Write ", " Else Debug.Write " "
        u = u + 1
    Next
    Debug.WriteLine "_"
Next
Debug.WriteLine ")"

代码无误的话,应该会在调试框中得到如下代码(作为验证可以看看):

Dim CRC16_Table:CRC16_Table = Array( _
&H0000&, &H8005&, &H800F&, &H000A&, _
&H801B&, &H001E&, &H0014&, &H8011&, _
&H8033&, &H0036&, &H003C&, &H8039&, _
&H0028&, &H802D&, &H8027&, &H0022&, _
&H8063&, &H0066&, &H006C&, &H8069&, _
&H0078&, &H807D&, &H8077&, &H0072&, _
&H0050&, &H8055&, &H805F&, &H005A&, _
&H804B&, &H004E&, &H0044&, &H8041&, _
&H80C3&, &H00C6&, &H00CC&, &H80C9&, _
&H00D8&, &H80DD&, &H80D7&, &H00D2&, _
&H00F0&, &H80F5&, &H80FF&, &H00FA&, _
&H80EB&, &H00EE&, &H00E4&, &H80E1&, _
&H00A0&, &H80A5&, &H80AF&, &H00AA&, _
&H80BB&, &H00BE&, &H00B4&, &H80B1&, _
&H8093&, &H0096&, &H009C&, &H8099&, _
&H0088&, &H808D&, &H8087&, &H0082&, _
&H8183&, &H0186&, &H018C&, &H8189&, _
&H0198&, &H819D&, &H8197&, &H0192&, _
&H01B0&, &H81B5&, &H81BF&, &H01BA&, _
&H81AB&, &H01AE&, &H01A4&, &H81A1&, _
&H01E0&, &H81E5&, &H81EF&, &H01EA&, _
&H81FB&, &H01FE&, &H01F4&, &H81F1&, _
&H81D3&, &H01D6&, &H01DC&, &H81D9&, _
&H01C8&, &H81CD&, &H81C7&, &H01C2&, _
&H0140&, &H8145&, &H814F&, &H014A&, _
&H815B&, &H015E&, &H0154&, &H8151&, _
&H8173&, &H0176&, &H017C&, &H8179&, _
&H0168&, &H816D&, &H8167&, &H0162&, _
&H8123&, &H0126&, &H012C&, &H8129&, _
&H0138&, &H813D&, &H8137&, &H0132&, _
&H0110&, &H8115&, &H811F&, &H011A&, _
&H810B&, &H010E&, &H0104&, &H8101&, _
&H8303&, &H0306&, &H030C&, &H8309&, _
&H0318&, &H831D&, &H8317&, &H0312&, _
&H0330&, &H8335&, &H833F&, &H033A&, _
&H832B&, &H032E&, &H0324&, &H8321&, _
&H0360&, &H8365&, &H836F&, &H036A&, _
&H837B&, &H037E&, &H0374&, &H8371&, _
&H8353&, &H0356&, &H035C&, &H8359&, _
&H0348&, &H834D&, &H8347&, &H0342&, _
&H03C0&, &H83C5&, &H83CF&, &H03CA&, _
&H83DB&, &H03DE&, &H03D4&, &H83D1&, _
&H83F3&, &H03F6&, &H03FC&, &H83F9&, _
&H03E8&, &H83ED&, &H83E7&, &H03E2&, _
&H83A3&, &H03A6&, &H03AC&, &H83A9&, _
&H03B8&, &H83BD&, &H83B7&, &H03B2&, _
&H0390&, &H8395&, &H839F&, &H039A&, _
&H838B&, &H038E&, &H0384&, &H8381&, _
&H0280&, &H8285&, &H828F&, &H028A&, _
&H829B&, &H029E&, &H0294&, &H8291&, _
&H82B3&, &H02B6&, &H02BC&, &H82B9&, _
&H02A8&, &H82AD&, &H82A7&, &H02A2&, _
&H82E3&, &H02E6&, &H02EC&, &H82E9&, _
&H02F8&, &H82FD&, &H82F7&, &H02F2&, _
&H02D0&, &H82D5&, &H82DF&, &H02DA&, _
&H82CB&, &H02CE&, &H02C4&, &H82C1&, _
&H8243&, &H0246&, &H024C&, &H8249&, _
&H0258&, &H825D&, &H8257&, &H0252&, _
&H0270&, &H8275&, &H827F&, &H027A&, _
&H826B&, &H026E&, &H0264&, &H8261&, _
&H0220&, &H8225&, &H822F&, &H022A&, _
&H823B&, &H023E&, &H0234&, &H8231&, _
&H8213&, &H0216&, &H021C&, &H8219&, _
&H0208&, &H820D&, &H8207&, &H0202& _
)

我们可以以空间换取时间,直接将CRC表格计算好作为一个常数数组使用。

得到表格之后回到驱动表法的计算:取出CRC寄存器中的首位字节,然后将CRC左移去掉首字节然后取出一字节新数据装入CRC低端空出字节中,根据取出首字节找到对应表格中的生成项之和与CRC寄存器进行Xor,然后重复这个步骤直到数据全部取完计算完。要注意的是:因为驱动表法是一个一个字节计算,所以他必须计算之前在原始数据上补零(不能像直接计算法那样通过逐位左移方式自己完成补零)。我们来看一下代码是如何实现的:

Dim CRC16_Table                     '这里表格我们直接就套用上一节我们计算好的数据
Const TEXT = "123456789"            '这里就是原始数据

'这里要注意驱动表法没有逐位补零,所以我们要手动在数据末尾增加两个字节的零
Dim str:str = TEXT & String(2,Chr(0))

Dim L,I,T,CRC
'初始化CRC寄存器值为0
CRC = 0
Do While L < Len(str)
    L = L + 1
    '取出CRC寄存器中的首字节
    T = (CRC And &HFF00&) / &H100
    '左移数据一个字节(就是去掉寄存器中的首字节),并在空出的字节放入新的数据,限制寄存器大小为两个字节。
    CRC = ((CRC * &H100&) Or Asc(Mid(str,L,1))) And &HFFFF&
    '将已经去掉首字节的CRC寄存器与对应的表格生成项之和进行Xor
    CRC = CRC Xor CRC16_Table(T)
    '当然也可以直接将上面三句缩写成一句:
    'CRC = (((CRC * 256) Or Asc(Mid(str,L,1))) And &HFFFF&) Xor Table16((CRC And &HFF00&) / &H100)
Loop

'限制CRC大小为两个字节
CRC = CRC And &HFFFF&

WScript.Echo Hex(CRC)

输出结果为:FEE8(注意这个值并不是标准的CRC16验证码,后面我们还会讲到如何修改它。)

直驱动法


这个部分的思路完全来自poiu_elab大佬的【脑冻结】CRC我就拿下了,感谢大佬的分享!

我们不妨用大佬的列子看看(丑不要脸的偷懒):

图片描述

我们针对31 32 33 34进行CRC-CCITT驱动表法的计算,着重观察每次查询表格的索引(也就是黄色部分),你会发现实际上索引就是原始数据与寄存器前2位数据Xor计算的结果。

比如第一次查询时,寄存器为0与原始数据31 Xor得到查表的索引31,查得26 72并存入寄存器内;遇到第二个原始数据32与寄存器内的26进行Xor得到14,查得52 B5由于寄存器左移一个字节于是上一次72移到高位与52进行Xor得到20,于是现在寄存器内就是20(72 Xor 52) B5;遇到第三个原始数据33与寄存器内20(72 xor 52)进行Xor,得到13……重复这个过程直到查到最后一个原始数据为止。

通过上面的步骤我们可以整理出直驱动表法的算法:

伪语言版:

循环:取得字符
寄存器 = 去掉首位字节的寄存器 Xor表格[寄存器的首字节 Xor 取出的原始数据]

C语言版 CRC32直驱表法:

While(len--)
r=(r<<8)^t[(r>>24)^*p++];

我个人的总结直驱动表法实际上就是将驱动表法再一次简化,将数据输入和表格查询有机的结合在一起,这么做好处可以不用在原始数据上补零,方便持续计算CRC(不停的加入新的数据)。

由于vbscript对于32位数据进行四则运算(乘法补零)会溢出,所以不妨为我们尝试一下寄存器分段处理(就是将寄存器分成两个部分处理来解决数据溢出的问题),代码如下:

Dim Table_M(255)         '表格的高位
Dim Table_L(255)         '表格的低位

'// CRC32正序表格计算
Const PLOY_M = &H04C1&   '生成项高位,CRC32生成项:04C11DB7
Const PLOY_L = &H1DB7&   '生成项低位
Dim I,J,CRC_M,CRC_L
For I = 0 To 255
    CRC_M = (I * &H100&) And &HFFFF&
    CRC_L = 0
    For J = 0 To 7
        If (CRC_M And &H8000) Then
            '// 左移
            CRC_M = (CRC_M And &H7FFF) * &H2
            CRC_M = CRC_M Xor ((CRC_L And &H8000&) / &H8000&) '低位寄存器中高位会移动到高位寄存器中的低位
            CRC_L = (CRC_L And &H7FFF) * &H2
            '// Xor计算
            CRC_M = CRC_M Xor PLOY_M
            CRC_L = CRC_L Xor PLOY_L
        Else
            '// 左移
            CRC_M = (CRC_M And &H7FFF) * &H2
            CRC_M = CRC_M Xor ((CRC_L And &H8000&) / &H8000&)
            CRC_L = (CRC_L And &H7FFF) * &H2
        End If
        Table_M(I) = CRC_M
        Table_L(I) = CRC_L
    Next
Next

'// CRC32计算部分
Const TEXT = "123456789"  '原始数据
CRC_M = 0
CRC_L = 0
Dim T
Do While L < Len(TEXT)
    L = L + 1
    '计算出查询表格的索引
    T = ((CRC_M And &HFF00&) / &H100&) Xor Asc(Mid(TEXT,L,1))
    '分别计算出高位、低位寄存器,要注意的是低位寄存器中高位会移动到高位寄存器中的低位。
    CRC_M = ((CRC_M And &HFF&) * &H100) Xor ((CRC_L And &HFF00&) / &H100&) Xor Table_M(T)
    CRC_L = ((CRC_L And &HFF&) * &H100) Xor Table_L(T)
Loop

'记得输出不足CRC寄存器长度的数据时在前面补零
Debug.WriteLine String(4 - Len(Hex(CRC_M)),"0"),Hex(CRC_M),String(4 - Len(Hex(CRC_L)),"0"),Hex(CRC_L)

输出结果为:89A1897F

CRC参数模型


细心的朋友可能发现我们上一节计算出的CRC32验证值是不对的,为了方便机器更好的计算CRC所以制定一些规则,称为CRC参数模型。我们一睹CRC32模型的芳容:

图片描述

Width:代表生成项长度
Poly:生成项
Init:寄存器计算前的初始值,其实初始值是为了保存数据之前零(CRC计算特性是省略开头的零)
RefIn:输入原始数据进行二进制数据反转,因为数据输入是一个字节一个字节,所以反转也是一个字节,如图所示:
图片描述
RefOut:最后输出的CRC数据进行数据反转,注意是整个CRC数据进行反转(其实就是对CRC寄存器进行反转)
XorOut:对已经RefOut的CRC数据进行Xor处理,方式和Init一样。
Check:是对字符串“123456789”CRC计算的验证值,作为参考看看自己的程序计算是否有误。

根据这个模型,我们对上一节的CRC32算法再次魔改:

Dim Table_M:Table_M = Array( _  ' CRC32表格高位,由于篇幅有限请自行补齐代码
Dim Table_L:Table_L = Array( _  ' CRC32表格低位,由于篇幅有限请自行补齐代码


'颠倒二进制函数
Public Function RevBin(ByVal Value,ByVal lLen)
    RevBin = 0
    If IsNumeric(Value) And Value Then
        lLen = lLen - 1
        Dim I,REG
        For I = 0 To lLen
            If ((2^I) And Value) Then REG = REG Or 2^(lLen - I)
        Next
        RevBin = REG
    End If
End Function

'原始数据
Const TEXT = "123456789" 

'Init:初始化寄存器
CRC_M = &HFFFF&
CRC_L = &HFFFF&

'计算CRC部分
Dim T
Do While L < Len(TEXT)
    L = L + 1
    'RefIn:注意这里的对一个字节(8bit)的反转(我曾经在这里被坑过)
    T = ((CRC_M And &HFF00&) / &H100) Xor RevBin(Asc(Mid(TEXT,L,1)),8)
    CRC_M = ((CRC_M And &HFF) * &H100) Xor ((CRC_L And &HFF00&) / &H100) Xor Table_M(T)
    CRC_L = ((CRC_L And &HFF) * &H100) Xor Table_L(T)
Loop

'RefOut:反转计算出来的CRC值
Dim Temp
Temp = RevBin(CRC_L,16)
CRC_L = RevBin(CRC_M,16)
CRC_M = Temp

'XorOut:最后一次Xor
CRC_M = CRC_M Xor &HFFFF&
CRC_L = CRC_L Xor &HFFFF&

'输出CRC验证值
Debug.WriteLine String(4 - Len(Hex(CRC_M)),"0"),Hex(CRC_M),String(4 - Len(Hex(CRC_L)),"0"),Hex(CRC_L)

终于得到正确的CRC32验证码:CBF43926

进一步的优化:镜像CRC直驱表法算法


虽然可以计算出CRC32,但是每次都要颠倒输入、输出的值这显十分浪费性能。我们可以不可以将这个算法直接镜像,从而避免对其输入、输出的逆转,只需要对表格进行逆转和相反的数据位移方向即可。

图片描述

那么新的问题已经出现,我们怎能停滞不前:这逆转的表格咋算啊?!

答案就是:要用魔法(逆转)去打败魔法(逆转):将生成项:04C11DB7逆转为EDB88320,新数据插入的方向为数据低位(新数据不需要逆转只需要通过它的值计算出对应的生成项之和),数据位移方向向右(这个就是逆转的核心)。我们可以达到以下代码:

Dim Table_M(255)         '表格的高位
Dim Table_L(255)         '表格的低位

'// CRC32逆序表格计算,CRC32生成项:EDB88320
Const PLOY_M = &HEDB8&   '生成项高位
Const PLOY_L = &H8320&   '生成项低位
Dim I,J,CRC_M,CRC_L
For I = 0 To 255
    CRC_M = 0
    CRC_L = I
    For J = 0 To 7
        If (CRC_L And &H1) Then
            '// 右移
            CRC_L = (CRC_L And &HFFFE&) / &H2
            CRC_L = CRC_L Xor ((CRC_M And &H1) * &H8000&) '高位寄存器中低位会移动到低位寄存器中的高位
            CRC_M = (CRC_M And &HFFFE&) / &H2
            '// Xor计算
            CRC_M = CRC_M Xor PLOY_M
            CRC_L = CRC_L Xor PLOY_L
        Else
            '// 右移
            CRC_L = (CRC_L And &HFFFE&) / &H2
            CRC_L = CRC_L Xor ((CRC_M And &H1) * &H8000&)  
            CRC_M = (CRC_M And &HFFFE&) / &H2
        End If
    Next
    Table_M(I) = CRC_M
    Table_L(I) = CRC_L
Next

'//输出逆序表格代码
Public Sub Print(ByVal Name,ByVal lTable)
    Dim y,x,u
    Debug.WriteLine Name & " = Array( _"
    For y = 1 To 32
        For x = 1 To 8
            Debug.Write "&H",String(4 - Len(Hex(lTable(u))),"0"),Hex(lTable(u)),"&"
            If u <> 255 Then Debug.Write ", " Else Debug.Write " "
            u = u + 1
        Next
        Debug.WriteLine "_"
    Next
    Debug.WriteLine ")"
End Sub

Call Print("CRC32Table_MSB",Table_M)
Call Print("CRC32Table_LSB",Table_L)

计算出表格代码根据上图的镜像算法,我们可以将新数据xor到寄存器最低位取得表格索引,然后两个寄存器左移一个字节计算索引对应的逆转的生成项之和。封装一下代码就是这样的:

Class C_CRC
    '初始化类载入数据
    Private m_CRC32Table_MSB
    Private m_CRC32Table_LSB
    Private Sub Class_Initialize()
        m_CRC32Table_MSB = Array( _
        &H0000&, &H7707&, &HEE0E&, &H9909&, &H076D&, &H706A&, &HE963&, &H9E64&, _
        &H0EDB&, &H79DC&, &HE0D5&, &H97D2&, &H09B6&, &H7EB1&, &HE7B8&, &H90BF&, _
        &H1DB7&, &H6AB0&, &HF3B9&, &H84BE&, &H1ADA&, &H6DDD&, &HF4D4&, &H83D3&, _
        &H136C&, &H646B&, &HFD62&, &H8A65&, &H1401&, &H6306&, &HFA0F&, &H8D08&, _
        &H3B6E&, &H4C69&, &HD560&, &HA267&, &H3C03&, &H4B04&, &HD20D&, &HA50A&, _
        &H35B5&, &H42B2&, &HDBBB&, &HACBC&, &H32D8&, &H45DF&, &HDCD6&, &HABD1&, _
        &H26D9&, &H51DE&, &HC8D7&, &HBFD0&, &H21B4&, &H56B3&, &HCFBA&, &HB8BD&, _
        &H2802&, &H5F05&, &HC60C&, &HB10B&, &H2F6F&, &H5868&, &HC161&, &HB666&, _
        &H76DC&, &H01DB&, &H98D2&, &HEFD5&, &H71B1&, &H06B6&, &H9FBF&, &HE8B8&, _
        &H7807&, &H0F00&, &H9609&, &HE10E&, &H7F6A&, &H086D&, &H9164&, &HE663&, _
        &H6B6B&, &H1C6C&, &H8565&, &HF262&, &H6C06&, &H1B01&, &H8208&, &HF50F&, _
        &H65B0&, &H12B7&, &H8BBE&, &HFCB9&, &H62DD&, &H15DA&, &H8CD3&, &HFBD4&, _
        &H4DB2&, &H3AB5&, &HA3BC&, &HD4BB&, &H4ADF&, &H3DD8&, &HA4D1&, &HD3D6&, _
        &H4369&, &H346E&, &HAD67&, &HDA60&, &H4404&, &H3303&, &HAA0A&, &HDD0D&, _
        &H5005&, &H2702&, &HBE0B&, &HC90C&, &H5768&, &H206F&, &HB966&, &HCE61&, _
        &H5EDE&, &H29D9&, &HB0D0&, &HC7D7&, &H59B3&, &H2EB4&, &HB7BD&, &HC0BA&, _
        &HEDB8&, &H9ABF&, &H03B6&, &H74B1&, &HEAD5&, &H9DD2&, &H04DB&, &H73DC&, _
        &HE363&, &H9464&, &H0D6D&, &H7A6A&, &HE40E&, &H9309&, &H0A00&, &H7D07&, _
        &HF00F&, &H8708&, &H1E01&, &H6906&, &HF762&, &H8065&, &H196C&, &H6E6B&, _
        &HFED4&, &H89D3&, &H10DA&, &H67DD&, &HF9B9&, &H8EBE&, &H17B7&, &H60B0&, _
        &HD6D6&, &HA1D1&, &H38D8&, &H4FDF&, &HD1BB&, &HA6BC&, &H3FB5&, &H48B2&, _
        &HD80D&, &HAF0A&, &H3603&, &H4104&, &HDF60&, &HA867&, &H316E&, &H4669&, _
        &HCB61&, &HBC66&, &H256F&, &H5268&, &HCC0C&, &HBB0B&, &H2202&, &H5505&, _
        &HC5BA&, &HB2BD&, &H2BB4&, &H5CB3&, &HC2D7&, &HB5D0&, &H2CD9&, &H5BDE&, _
        &H9B64&, &HEC63&, &H756A&, &H026D&, &H9C09&, &HEB0E&, &H7207&, &H0500&, _
        &H95BF&, &HE2B8&, &H7BB1&, &H0CB6&, &H92D2&, &HE5D5&, &H7CDC&, &H0BDB&, _
        &H86D3&, &HF1D4&, &H68DD&, &H1FDA&, &H81BE&, &HF6B9&, &H6FB0&, &H18B7&, _
        &H8808&, &HFF0F&, &H6606&, &H1101&, &H8F65&, &HF862&, &H616B&, &H166C&, _
        &HA00A&, &HD70D&, &H4E04&, &H3903&, &HA767&, &HD060&, &H4969&, &H3E6E&, _
        &HAED1&, &HD9D6&, &H40DF&, &H37D8&, &HA9BC&, &HDEBB&, &H47B2&, &H30B5&, _
        &HBDBD&, &HCABA&, &H53B3&, &H24B4&, &HBAD0&, &HCDD7&, &H54DE&, &H23D9&, _
        &HB366&, &HC461&, &H5D68&, &H2A6F&, &HB40B&, &HC30C&, &H5A05&, &H2D02& )
        m_CRC32Table_LSB = Array( _
        &H0000&, &H3096&, &H612C&, &H51BA&, &HC419&, &HF48F&, &HA535&, &H95A3&, _
        &H8832&, &HB8A4&, &HE91E&, &HD988&, &H4C2B&, &H7CBD&, &H2D07&, &H1D91&, _
        &H1064&, &H20F2&, &H7148&, &H41DE&, &HD47D&, &HE4EB&, &HB551&, &H85C7&, _
        &H9856&, &HA8C0&, &HF97A&, &HC9EC&, &H5C4F&, &H6CD9&, &H3D63&, &H0DF5&, _
        &H20C8&, &H105E&, &H41E4&, &H7172&, &HE4D1&, &HD447&, &H85FD&, &HB56B&, _
        &HA8FA&, &H986C&, &HC9D6&, &HF940&, &H6CE3&, &H5C75&, &H0DCF&, &H3D59&, _
        &H30AC&, &H003A&, &H5180&, &H6116&, &HF4B5&, &HC423&, &H9599&, &HA50F&, _
        &HB89E&, &H8808&, &HD9B2&, &HE924&, &H7C87&, &H4C11&, &H1DAB&, &H2D3D&, _
        &H4190&, &H7106&, &H20BC&, &H102A&, &H8589&, &HB51F&, &HE4A5&, &HD433&, _
        &HC9A2&, &HF934&, &HA88E&, &H9818&, &H0DBB&, &H3D2D&, &H6C97&, &H5C01&, _
        &H51F4&, &H6162&, &H30D8&, &H004E&, &H95ED&, &HA57B&, &HF4C1&, &HC457&, _
        &HD9C6&, &HE950&, &HB8EA&, &H887C&, &H1DDF&, &H2D49&, &H7CF3&, &H4C65&, _
        &H6158&, &H51CE&, &H0074&, &H30E2&, &HA541&, &H95D7&, &HC46D&, &HF4FB&, _
        &HE96A&, &HD9FC&, &H8846&, &HB8D0&, &H2D73&, &H1DE5&, &H4C5F&, &H7CC9&, _
        &H713C&, &H41AA&, &H1010&, &H2086&, &HB525&, &H85B3&, &HD409&, &HE49F&, _
        &HF90E&, &HC998&, &H9822&, &HA8B4&, &H3D17&, &H0D81&, &H5C3B&, &H6CAD&, _
        &H8320&, &HB3B6&, &HE20C&, &HD29A&, &H4739&, &H77AF&, &H2615&, &H1683&, _
        &H0B12&, &H3B84&, &H6A3E&, &H5AA8&, &HCF0B&, &HFF9D&, &HAE27&, &H9EB1&, _
        &H9344&, &HA3D2&, &HF268&, &HC2FE&, &H575D&, &H67CB&, &H3671&, &H06E7&, _
        &H1B76&, &H2BE0&, &H7A5A&, &H4ACC&, &HDF6F&, &HEFF9&, &HBE43&, &H8ED5&, _
        &HA3E8&, &H937E&, &HC2C4&, &HF252&, &H67F1&, &H5767&, &H06DD&, &H364B&, _
        &H2BDA&, &H1B4C&, &H4AF6&, &H7A60&, &HEFC3&, &HDF55&, &H8EEF&, &HBE79&, _
        &HB38C&, &H831A&, &HD2A0&, &HE236&, &H7795&, &H4703&, &H16B9&, &H262F&, _
        &H3BBE&, &H0B28&, &H5A92&, &H6A04&, &HFFA7&, &HCF31&, &H9E8B&, &HAE1D&, _
        &HC2B0&, &HF226&, &HA39C&, &H930A&, &H06A9&, &H363F&, &H6785&, &H5713&, _
        &H4A82&, &H7A14&, &H2BAE&, &H1B38&, &H8E9B&, &HBE0D&, &HEFB7&, &HDF21&, _
        &HD2D4&, &HE242&, &HB3F8&, &H836E&, &H16CD&, &H265B&, &H77E1&, &H4777&, _
        &H5AE6&, &H6A70&, &H3BCA&, &H0B5C&, &H9EFF&, &HAE69&, &HFFD3&, &HCF45&, _
        &HE278&, &HD2EE&, &H8354&, &HB3C2&, &H2661&, &H16F7&, &H474D&, &H77DB&, _
        &H6A4A&, &H5ADC&, &H0B66&, &H3BF0&, &HAE53&, &H9EC5&, &HCF7F&, &HFFE9&, _
        &HF21C&, &HC28A&, &H9330&, &HA3A6&, &H3605&, &H0693&, &H5729&, &H67BF&, _
        &H7A2E&, &H4AB8&, &H1B02&, &H2B94&, &HBE37&, &H8EA1&, &HDF1B&, &HEF8D& )
    End Sub
    
    '// 计算CRC部分
    Public Function CRC32(ByVal Value)
        Dim CRC_MSB,CRC_LSB
        CRC_MSB = &HFFFF&
        CRC_LSB = &HFFFF&
        
        Dim StrLen:StrLen = Len(Value)
        Dim FirstByte,L
        Do While L < StrLen
            L = L + 1
            FirstByte = (CRC_LSB And &HFF&) Xor Asc(Mid(Value,L,1))
            CRC_LSB = ((CRC_LSB And &HFF00&) / &H100) Xor ((CRC_MSB And &HFF&) * &H100&) Xor m_CRC32Table_LSB(FirstByte)
            CRC_MSB = ((CRC_MSB And &HFF00&) / &H100) Xor m_CRC32Table_MSB(FirstByte)
        Loop
        
        CRC_MSB = CRC_MSB Xor &HFFFF&
        CRC_LSB = CRC_LSB Xor &HFFFF&
        
        CRC32 = String(4 - Len(Hex(CRC_MSB)),"0") & Hex(CRC_MSB) & String(4 - Len(Hex(CRC_LSB)),"0") & Hex(CRC_LSB)
    End Function
End Class

Dim CRC:Set CRC = New C_CRC
WScript.Echo CRC.CRC32("123456789")

到这里教程就完全结束了,当然我这个代码完全没有效率,纯粹写出演示闹着玩(笑)。如果是想用vbs进行CRC计算的话,我推荐Demon大佬的这篇Blog:用VBS实现PHP的crc32函数。使用MSScriptControl.ScriptControl对象调用js就可以十分方便的实现数据数据左右移。可读性远比我这篇代码要高的多,推荐去读一读方便理解。

其他博客推荐

虽说我码这么多字,但我还是没有自信能保证各位一定能懂(强行语文老师背锅),所以我个人觉得写的不错的几篇博客(也是我学习的博客),希望能对大家有用。

循环冗余校验(CRC)算法入门引导:https://blog.csdn.net/liyuanb...
我学习CRC32、CRC16、CRC原理和算法的总结(与WINRAR结果一致):https://wenku.baidu.com/view/...
【脑冻结】CRC我就拿下了:https://www.cnblogs.com/poiu-...
CRC的基本原理详解:https://blog.csdn.net/dream_1...
循环冗余检验 (CRC) 算法原理:http://www.cnblogs.com/esestt...
你真的搞明白CRC的计算过程了吗?:http://blog.chinaaet.com/wuya...
CRC检错技术原理:https://www.cnblogs.com/forge...
CRC算法详解与c源码:https://wenku.baidu.com/view/...
最通俗的CRC校验原理剖析:http://blog.51cto.com/winda/1...
VB的CRC32校验代码:https://blog.csdn.net/zdingyu...
CRC校验码原理、实例、手动计算:https://www.cnblogs.com/bugut...
如何计算CRC32校验和?:https://codeday.me/bug/201708...
CRC算法与实现:https://blog.csdn.net/ncdawen...

再推荐两个在线计算网站方便验证程序:

CRC(循环冗余校验)在线计算:http://www.ip33.com/crc.html
On-line CRC calculation and free library:https://www.lammertbies.nl/co...

当然如果你E文比较好的话,还是推荐Ross Williams的《A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS》 http://www.ross.net/crc/downl...

哦,对了!故事的最后:王大麻子其实喜欢他们班的班长——小强

阅读 25.3k

只凭一己之力,拉低行业水准。

13 声望
7 粉丝
0 条评论
你知道吗?

只凭一己之力,拉低行业水准。

13 声望
7 粉丝
宣传栏