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

24次阅读

共计 21404 个字符,预计需要花费 54 分钟才能阅读完成。

不要跑,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…
哦,对了!故事的最后:王大麻子其实喜欢他们班的班长——小强

正文完
 0