打印
[微控制器/MCU]

CRC原理及C语言实现

[复制链接]
6072|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Massif123|  楼主 | 2010-2-3 12:42 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
CRC原理介绍:
CRC的英文全称为Cyclic Redundancy Check(Code),中文名称为循环冗余校验(码)。它是一类重要的线性分组码,编码和解码方法简单,检错和纠错能力强,在通信领域广泛地用于实现差错控制。   

     CRC计算与普通的除法计算有所不同。普通的除法计算是借位相减的,而CRC计算则是异或运算。任何一个除法运算都需要选取一个除数,在CRC运算中我们称之为poly,而宽度W就是poly最高位的位置。比如poly 1001的W是3,而不是4。注意最高位总是1,当你选定一个宽度,那么你只需要选择低W各位的值。假如我们想计算一个位串的CRC码,并要保证每一位都要被处理,因此我们需要在目标位串后面加上W个0。下面举例说明CRC算法的过程。

     在此例中,我们假设位串为110101101。

Poly                     = 10011(宽度W = 4)
Bitstring + W zeros = 110101101 0000  

10011/1101011010000/110000101 (我们不关心此运算的商)
      10011||||||||  
       -----||||||||
        10011|||||||
        10011|||||||
         -----|||||||
          00001||||||
          00000||||||
           -----||||||
            00010|||||
            00000|||||
             -----|||||
              00101||||
              00000||||
               -----||||
                01010|||
                00000|||  
                 -----|||
                  10100||
                  10011||
                   -----||
                    01110|
                    00000|
                     -----|
                     11100
                     10011
                      -----
                       1111 -> 余数 -> CRC!

计算过程总结如下:
1. 只有当位串的最高位为1,我们才将它与poly做XOR运算,否则我们只是将位串左移一位。
2. 异或运算的结果实质上是被操作位串与poly的低W位进行运算的结果,因为最高位总为0。 

  

CRC原理及其逆向**方法:

介绍:

  这篇短文包含CRC原理介绍和其逆向分析方法,很多程序员和**者不是很清楚了解
CRC的工作原理,而且几乎没人知道如何逆向分析它的方法,事实上它是非常有用的.
首先,这篇教程教你一般如何计算CRC,你可以将它用在数据代码保护中.第二,主要是
介绍如何逆向分析CRC-32,你可以以此来分析程序中的CRC保护(象反病毒编码).当然
有很多有效的工具用来对付CRC,但我怀疑它是否会说明原理.
  我要告诉你,这篇短文里中应用了很多数学知识,这不会影响一些人,而且会被一般的
程序员与逆向分析者很好理解.为什么?那么如果你不知道数学是如何被应用在CRC中,
我建议你可以停止继续学习了.所以我假定你们(读者)都是具备二进制算术知识的.

第一部分:CRC 介绍,CRC是什么和计算CRC的方法.   

循环冗余码 CRC

  我们都知道CRC.甚至你没有印象,但当你想到那些来自诸如RAR,ZIP等压缩软件发给你
由于错误连接和其他一些意外原因导致的文件错误的恼人的消息时,你就会知道.CRC是块
数据的计算值,比如对每一个文件进行压缩.在一个解压缩过程中,程序会从新计算解压文件
的CRC值,并且将之与从文件中读取的CRC值进行比对,如果值相同,那么正确.在CRC-32中,
会有1/2^32的可能性发生对确认数据更改的校验错误.     
  很多人认为CRC就是循环冗余校验,假如CRC真的就是循环冗余校验,那么很多人都错用了
这个术语.你不能说"这个程序的CRC是12345678".人们也常说某一个程序有CRC校验,而不
说是 "循环冗余校验" 校验.结论:CRC 代表循环冗余码,而不是循环冗余校验.   
  计算是如何完成的呢?好,主要的想法就是将一个文件看成一个被一些数字分割的很长的
位字串,这里会有一个余数---CRC!你总会有一个余数(可以是0),它至多比除数小一.
(9/3=3 余数=0 ; (9+2)/3=3 余数=2)
(或者它本身就包含一个除数在其中).  
  在这里CRC计算方法与除法有一点点区别,除法就是将被减数重复的减去除数X次,然后留下
余数.如果你希望得到原值,那么你就要把除数乘上X次,然后加上余数.
  CRC计算使用特殊的减法与加法完成的.也就是一种新的"算法".计算中每一位计算的进位值
被"遗忘"了.   
看如下两个例子,1是普通减法,2和3是特殊的.
     -+
(1) 1101  (2) 1010  1010  (3) 0+0=0  0-0=0
    1010-     1111+ 1111-     0+1=1 *0-1=1
    ----      ----  ----      1+0=1  1-0=1
    0011      0101  0101     *1+1=0  1-1=0
  在(1)中,右数第二列可以看成是0-1=-1,因此要从高位借1,就变成(10+0)-1=1.(这就象普通
的'by-paper'十进制减法).特例(2,3)中,1+1会有正常的结果10,'1'是计算后的进位.这个值
被忽略了.特殊情况0-1应该有正常结果'-1'就要退到下一位.这个值也被忽略了.假如你对编程
有一定了解,这就象,XOR 操作或者更好.
  现在来看一个除法的例子:

相关帖子

沙发
Massif123|  楼主 | 2010-2-3 12:42 | 只看该作者
在普通算法中:
1001/1111000\1101 13            9/120\13
     1001    -                    09  -|
     ----                         --   |
      1100                         30  |
      1001    -                    27  -
      ----                         --
       0110                         3 -> 余数
       0000    -
       ----
        1100
        1001    -
        ----
         011 -> 3, 余数

在CRC算法中:
1001/1111000\1110               9/120\14 余数为 6
     1001    -
     ----
      1100
      1001    -
      ----
       1010
       1001    -
       ----
        0110
        0000    -
        ----
         110 -> 余数
(例 3)

  这个除法的商并不重要,也没必要去记住,因为他们仅仅是一组无关紧要的位串.真正
重要的是余数!它就是这个值,可以说比原文件还重要的值,他就是基本的CRC.


过度到真正的CRC码计算.

  进行一个CRC计算我们需要选则一个除数,从现在起我们称之为"poly".宽度W就是最高位
的位置,所以这个poly 1001的W 是3,而不是4.注意最高位总是1,当你选定一个宽度,那么你只
需要选择低W各位的值.   
  假如我们想计算一个位串的CRC码,我们想确定每一个位都被处理过,因此,我们要在目标
位串后面加上W个0位.在此例中,我们假设位串为1111.请仔细分析下面一个例子:

Poly                = 10011, 宽度 W=4
位串                Bitstring  
Bitstring + W zeros = 110101101 + 0000

10011/1101011010000\110000101 (我们不关心此运算的商)
      10011|||||||| -
      -----||||||||
       10011|||||||
       10011|||||||  -
       -----|||||||
        00001||||||
        00000||||||   -
        -----||||||
         00010|||||
         00000|||||    -
         -----|||||
          00101||||
          00000||||     -
          -----||||
           01010|||
           00000|||      -
           -----|||
            10100||
            10011||       -
            -----||
             01110|
             00000|        -
             -----|
              11100
              10011         -
              -----
               1111 -> 余数 -> the CRC!
(例 4)

重要两点声明如下:
1.只有当Bitstring的最高位为1,我们才将它与poly做XOR运算,否则我们只是将
  Bitstring左移一位.
2.XOR运算的结果就是被操作位串bitstring与低W位进行XOR运算,因为最高位总为0.

使用特权

评论回复
板凳
Massif123|  楼主 | 2010-2-3 12:43 | 只看该作者
算法设计:

  你们都应知道基于位运算的算法是非常慢的而且效率低下.但如果将计算放在每一字节上
进行,那么效率将大大提高.不过我们只能接受poly的宽度是8的倍数(一个字节;).可以形
象的看成这样一个宽度为32的poly(W=32):

          3   2   1   0    byte
        +---+---+---+---+
Pop! <--|   |   |   |   |<-- bitstring with W zero bits added, in this case 32
        +---+---+---+---+
       1<--- 32 bits ---> this is the poly, 4*8 bits

(figure 1)
  这是一个你用来存放暂时CRC结果的记存器,现在我称它为CRC记存器或者记存器.你从右
至左移动位串,当从左边移出的位是1,则整个记存器被与poly的低W位进行XOR运算.(此例
中为32).事实上,我们精确的完成了上面除法所做的事情.


移动前记存器值为:10110100
当从右边移入4位时,左边的高4位将被移出,此例中1011将被移出,而1101被移入.

情况如下:
当前8位CRC记存器      : 01001101
刚刚被移出的高4位     : 1011
我们用此poly          : 101011100, 宽度 W=8

现在我们用如前介绍的方法来计算记存器的新值.
顶部  记存器
---- --------
1011 01001101  高四位和当前记存器值
1010 11100   + (*1) Poly 放在顶部最高位进行XOR运算 (因为那里是1)
-------------
0001 10101101 运算结果

现在我们仍有一位1在高4位:
0001 10101101  上一步结果
   1 01011100+ (*2) Poly 放在顶部的最低位进行XOR运算 (因为那里是1)
-------------
0000 11110001 第二步运算结果
^^^^
现在顶部所有位均为0,所以我们不需要在与poly进行XOR运算

你可以得到相同的结果如果你先将(*1)与(*2)做XOR然后将结果与记存器值做XOR.
这就是标准XOR运算的特性:
(a XOR b) XOR c = a XOR (b XOR c)  由此,推出如下的运算顺序也是正确的.

1010 11100       poly  (*1)    放在顶部最高位
   1 01011100+   polys (*2)    放在顶部最低位
-------------
1011 10111100  (*3) XOR运算结果

The result (*3)   将(*3)与记存器的值做XOR运算
1011 10111100
1011 01001101+    如右:
-------------
0000 11110001

你看到了吗?得到一样的结果!现在(*3)变的重要了,因为顶部为1010则(3)的值总是等于
10111100(当然是在一定的条件之下)这意味着你可以预先计算出任意顶部位结合的XOR值.
注意,顶部结果总是0,这就是组合XOR操作导致的结果.(翻译不准确,保留原文)

  现在我们回到figure 1,对每一个顶部字节的值都做移出操作,我们可以预先计算出一个值.
此例中,它将是一个包含256个double word(32 bit)双字的表.(附录中CRC-32的表).
(翻译不准确,保留原文)   

用伪语言表示我们的算法如下:  

While (byte string is not exhausted)
    Begin
    Top = top_byte of register ;
    Register = Register shifted 8 bits left ORred with a new byte from string ;
    Register = Register XORred by value from precomputedTable at position Top ;
    End

direct table算法:
  上面提到的算法可以被优化.字节串中的字节在被用到之前没有必要经过整个记村器.用
这个新的算法,我们可以直接用一个字节去XOR一个字节串通过将此字节移出记存器.结果
指向预先计算的表中的一个值,这个值是用来被记存器的值做XOR运算的.   
  我不十分确切的知道为什么这会得到同样的结果(这需要了解XOR运算的特性),但是这又
极为便利,因为你无须在你的字节串后填充0字节/位.(如果你知道原理,请告诉我:)  
  让我们来实现这个算法:  

  +----< byte string  (or file)  字节串,(或是文件)
  |
  v       3   2   1   0    byte  字节
  |     +---+---+---+---+
XOR---<|   |   |   |   |  Register  记存器
  |     +---+---+---+---+
  |             |
  |            XOR
  |             ^
  v     +---+---|---+---+
  |     |   |   |   |   |  Precomputed table 值表(用来进行操作)
  |     +---+---+---+---+
  +--->-:   :   :   :   :
        +---+---+---+---+
        |   |   |   |   |
        +---+---+---+---+
(figure 2)

'reflected' direct Table 算法:

使用特权

评论回复
地板
Massif123|  楼主 | 2010-2-3 12:43 | 只看该作者
由于这里有这样一个与之相对应的'反射'算法,事情显得复杂了.一个反射的值/记存器
就是将它的每一位以此串的中心位为标准对调形成的.例如:0111011001就是1001101110
的反射串.   
  他们提出'反射'是因为UART(一种操作IO的芯片)发送每一个字节时是先发最没用的0位,
最后再发最有意义的第七位.这与正常的位置是相逆的.   
  除了信息串不做反射以外,在进行下一步操作前,要将其于的数据都做反射处理.所以在
计算值表时,位向右移,且poly也是作过反射处理的.当然,在计算CRC时,记存器也要向右
移,而且值表也必须是反射过的.   

  byte string (or file) -->---+
                              |    1. 表中每一个入口都是反射的.
    byte  3   2   1   0       V    2. 初始化记存器也是反射的.
        +---+---+---+---+     |    3. 但是byte string中的数据不是反射的,
        |   |   |   |   |>---XOR      因为其他的都做过反射处理了.  
        +---+---+---+---+     |
                |             |
               XOR            V
                ^             |
        +---+---|---+---+     |
        |   |   |   |   |     |     值表
        +---+---+---+---+     |
        :   :   :   :   : <---+
        +---+---+---+---+
        |   |   |   |   |
        +---+---+---+---+
(figure 3)

我们的算法如下:
1. 将记存器向右移动一个字节.
2. 将刚移出的哪个字节与byte string中的新字节做XOR运算,
   得出一个指向值表table[0..255]的索引
3. 将索引所指的表值与记存器做XOR运算.
4. 如数据没有全部处理完,则跳到步骤1.


下面是这个算法的简单的可执行汇编源码:

完整的CRC-32标准所包含的内容:
Name            : "CRC-32"
Width           : 32
Poly            : 04C11DB7
Initial value   : FFFFFFFF
Reflected       : True
XOR out with    : FFFFFFFF

作为对你好奇心的奖励, 这里是CRC-16标准: :)
Name            : "CRC-16"
Width           : 16
Poly            : 8005
Initial value   : 0000
Reflected       : True
XOR out with    : 0000

'XOR out with' 是为了最终得到CRC而用来与记存器最后结果做XOR运算的值.
假如你想了解一些关于'reversed'逆向CRC poly的话,请看我的参考**.
   
  我是在16位DOS模式下用的32位编码,因此你会在这个程序中看到很多32位与16位混合
的编码...当然这是很容易转换成纯32位编码的.注意这个程序是经过完整测试并且能够
正常运行的.下面的Java 和 C 代码都是由这个汇编代码而来的.   
底下的这段程序就是用来计算CRC-32 table的:

        xor     ebx, ebx   ;ebx=0, 将被用做一个指针.
InitTableLoop:
        xor     eax, eax   ;eax=0 为计算新的entry.
        mov     al, bl     ;al<-bl

        ;生成入口.
        xor     cx, cx
entryLoop:
        test    eax, 1
        jz     no_topbit
        shr     eax, 1
        xor     eax, poly
        jmp    entrygoon
no_topbit:
        shr     eax, 1
entrygoon:
        inc     cx
        test    cx, 8
        jz     entryLoop

        mov     dword ptr[ebx*4 + crctable], eax
        inc     bx
        test    bx, 256
        jz     InitTableLoop

注释:  - crctable 是一个包含256个dword的数组.
       - 由于使用反射算法,EAX被向右移.
       - 因此最低的8位被处理了.

用Java和C写的代码如下(int is 32 bit):

for (int bx=0; bx<256; bx++){
  int eax=0;
  eax=eax&0xFFFFFF00+bx&0xFF;      // 就是 'mov al,bl' 指令
  for (int cx=0; cx<8; cx++){
    if (eax&&0x1) {
      eax>>=1;
      eax^=poly;
    }
    else eax>>=1;
  }
  crctable[bx]=eax;
}

下面的汇编代码是用来计算CRC-32的:

computeLoop:
        xor     ebx, ebx
        xor     al, [si]
        mov     bl, al
        shr     eax, 8
        xor     eax, dword ptr[4*ebx+crctable]
        inc     si
        loop   computeLoop

        xor     eax, 0FFFFFFFFh

注释:  - ds:si 指向将要被处理的byte string信息流.
       - cx 信息流的长度.
       - eax 是当前的CRC.
       - crctable是用来计算CRC的值表.
       - 此例中记存器的初始值为: FFFFFFFF.
       - 要将中间值与FFFFFFFFh做XOR才能得到CRC

下面是Java和C写的代码:

for (int cx=0; cx>=8;
   eax^=crcTable[ebx];
}
eax^=0xFFFFFFFF;

  现在我们已经完成了本文的第一部分:CRC原理部分,所以如果你希望能够对CRC做更深
的研究,那么我建议你去读在本文最后给出连接上的资料,我读了.好了,终于到了本文最
有意思的部分,CRC的逆向分析!

使用特权

评论回复
5
Massif123|  楼主 | 2010-2-3 12:43 | 只看该作者
第二部分 CRC的逆向分析:

  我遇到了很多障碍,当我思考如何**CRC时.我试图使用一些特殊顺序的字节使CRC无效.
但我没有做到...后来我意识到这种方法是行不同的,因为CRC内建了一些处理过程,无论你
改变任何位它都不会出问题,真正的CRC就是在不断变化的,总是在变化的.找一些CRC程序,
你可以自己尝试一下.   
  现在我知道我只能'纠正'在CRC后面的那些我想改变的字节.所以我要构造一个字节序列,
它可以将CRC转化成任何我想要的样子!   

具体实现这个想法

一个字节串?     01234567890123456789012345678901234567890123456789012
You want to change from  ^  this byte to  ^  this one.

就是位置9->26.
同时我们需要额外的4个字节用来在最后恢复原始字节串.

  当你计算CRC-32时,从0-8都没有问题,直到第9位,修补过的字节串会使CRC发生根本的改变.
即使当走过了第26位,以后的字节都没有改变,你也不可能在得到原始的CRC了,不可能了!你读
过后面的段落时就会明白为什么.间而言之,当你修改一个字节串时,要保证CRC不变.   

1. 计算并保存从1~9位的CRC.
2. 继续计算直到第27位还有额外的4字节并保存结果.
3. 用1的值来计算新的字节串和额外4字节的CRC(对应patch后的新的CRC值),并将之保存.
4. 现在我们得到了一个新的CRC,但是我们希望将它还原成原先的CRC,所以我们用逆向算法
   来计算那额外的4字节.

1~3就是实际的情况,下面你将学到最关键的部分4.


'反转'CRC-16

  我想,先来介绍计算逆CRC-16对于你来说会简单些.好的,我们现在处在一个恰当的位置,
在以修改代码后面,就是你想将CRC还原的地方.我们知道原始的CRC(是在patch代码之前计
算出来的)还有这个当前的记存器值.现在我们的目的就是计算可以改变当前记存器值到原
始记存器值的两个字节.首先,我们用正常的方法计算这两个未知字节的CRC.我们设他们为
X,Y.设记存器为a1,a0,只有0不能用来作为变量(00).:)在来看一下我们的CRC算法,figure
3,更好的理解下面我要做的.

好,我们开始:

用这两字节串'X Y' 字节是从左边开始被处理的.
记存器现在是a1 a0.
用'+'来表示XOR运算(和第一部分中用的一样)

处理第一个字节, X:
a0+X            这是顶部字节的计算结果 (1)
b1 b0           这是(1)在表中索引对象.
00 a1           向右移动记存器.
00+b1 a1+b0     上面两行对应位做XOR运算.

现在记存器为: (b1) (a1+b0)

处理第二个字, Y:
(a1+b0)+Y       此轮顶部字节的计算结果(2)
c1 c0           这是(2)在表中的索引对象.
00 b1           向右移动记存器.
00+c1 b1+c0     上面两行对应位做XOR运算.

最后记存器就是: (c1) (b1+c0)

我用一点不同的方法来表示:

a0 + X      =(1)  在表中指向b1 b0.
a1 + b0 + Y =(2)  在表中指向c1 c0.
     b1 + c0=d0   记存器中新的低位字节.
          c1=d1   记存器中新的高位字节.
    (1)  (2)

Wow! 请大家暂时记住上面的信息:)
别着急, 下面给出一个有具体值的例子.

使用特权

评论回复
6
Massif123|  楼主 | 2010-2-3 12:44 | 只看该作者
如果你想要的记存器的值是d1 d0(是原始的CRC),而且你知道在变换之前的记存器的值
(a1 a0)...那么你将要送如什么样的2个字节进记存器来做CRC计算呢?   
  好了,现在我们的工作应该从幕后走到台前来了.d0一定是bi+c0,并且d1一定是c1...
但是这到底是怎么回事,我听到你这样问了,你能知道b1和c0的值吗???你还记得哪个值表
吗?你只需要在表中查找c0 c1这个字的值就可以了因为你知道c1.所以你需要编写一个查
找程序.假如你找到了这个值,一定要记住这个值的索引,因为这就是找出未知的两个顶部
字节,举例来说:(1)和(2)!  
  所以,现在你找到了c1 c0,那么如何来得到b1 b0呢?如果b1+c0=d0那么b1=d0+c0!如前所
述,现在你用哪个查找程序在表中查b1 b0的值.现在我们得到了所有计算X和Y所需要的值.
Cool huh?   
a1+b0+Y=(2) so Y=a1+b0+(2)
a0+X=(1)    so X=a0+(1)


实例.

让我们来看看这个具体值的例子:
-register before: (a1=)DE (a0=)AD
-wanted register: (d1=)12 (d0=)34
在附录的CRC-16的表中查找以12开头值的入口.这里入口38h的值为12C0.试这找一找是否还
有以12开头的值的入口.你不可能在找到的,因为我们计算每一中顶部字节组合而得的值的
入口,一共是256个值,记住!
现在我们知道(2)=38,c1=12,c0=C0,所以b1=C0+34=F4,现在查找以F4开头的b1的入口.这里
入口4Fh的值是F441.
我们还知道  (1)=4F,b1=F4,b0=41,现在所有我们需要的都已经清楚了,接下来我们计算X,Y.

Y=a1+b0+(2)=DE+41+38=A7
X=a0+(1)   =AD+4F   =E2

结论:将CRC 记存器的值从 DEAD 变为 1234 我们需要这两个字节 E2 A7 (以此顺序).

  你看,**CRC校验你需要反向计算,还有要记住的就是计算过程中的值.当你在用汇编编写
查找表程序时,要注意intel在小模式中是反向存储值的.现在你可能已经明白如何去**这个
CRC-16了...下面介绍如何在CRC-32中实现.


** CRC-32

现在我们来看CRC-32,和CRC-16是一样容易的(可能一样的不容易你认为).这里你操作的对象
是4个字节的而不是2字节的.继续向下看,将它与上面CRC-16版本做对比.

设4字节串 X  Y  Z  W  , 从左边开始处理.
设记存器为  a3 a2 a1 a0
注意a3是MSB,而a0是LSB

处理第一个字节, X:
a0+X                    这是顶部字节的计算结果(1).
b3    b2    b1    b0    这是(1)在表中索引对象序列.
00    a3    a2    a1    右移记存器.
00+b3 a3+b2 a2+b1 a1+b0 上面两行对应位做XOR运算.

现在记存器是: (b3) (a3+b2) (a2+b1) (a1+b0)

Processing second byte, Y:
(a1+b0)+Y                       这是顶部字节的计算结果(2).
c3    c2    c1       c0         这是(2)在表中索引对象序列.
00    b3    a3+b2    a2+b1      右移记存器.
00+c3 b3+c2 a3+b2+c1 a2+b1+c0   上面两行对应位做XOR运算.

现在记存器是: (c3) (b3+c2) (a3+b2+c1) (a2+b1+c0)

Processing third byte, Z:
(a2+b1+c0)+Z                     这是顶部字节的计算结果(3).
d3    d2    d1       d0          这是(3)在表中索引对象序列.
00    c3    b3+c2    a3+b2+c1    右移记存器.
00+d3 c3+d2 b3+c2+d1 a3+b2+c1+d0 上面两行对应位做XOR运算.

现在记存器是: (d3) (c3+d2) (b3+c2+d1) (a3+b2+c1+d0)

Processing fourth byte, W:
(a3+b2+c1+d0)+W                  这是顶部字节的计算结果(4).
e3    e2    e1       e0          这是(4)在表中索引对象序列.
00    d3    c3+d2    b3+c2+d1    右移记存器.
00+e3 d3+e2 c3+d2+e1 b3+c2+d1+e0 上面两行对应位做XOR运算.

最后,记存器为: (e3) (d3+e2) (c3+d2+e1) (b3+c2+d1+e0)

我用一个不同一点的方法来表示:
a0 + X                  =(1)  在表中指向 b3 b2 b1 b0   
a1 + b0 + Y             =(2)  在表中指向 c3 c2 c1 c0   
a2 + b1 + c0 + Z        =(3)  在表中指向 d3 d2 d1 d0   
a3 + b2 + c1 + d0 + W   =(4)  在表中指向 e4 e3 e2 e1   
     b3 + c2 + d1 + e0  =f0
          c3 + d2 + e1  =f1
               d3 + e2  =f2
                    e3  =f3
    (1)  (2)  (3)  (4)
(figure 4)

这里是用的与CRC-16同样的方法来实现的,我会给出一个具体值的例子.查找用附录中
CRC-32的值表.

Take for CRC register before, a3 a2 a1 a0 -> AB CD EF 66
Take for CRC register after,  f3 f2 f1 f0 -> 56 33 14 78 (wanted value)

我们开始:

First byte of entries            entry   value
e3=f3                     =56 -> 35h=(4) 56B3C423 for e3 e2 e1 e0
d3=f2+e2      =33+B3      =E6 -> 4Fh=(3) E6635C01 for d3 d2 d1 d0
c3=f1+e1+d2   =14+C4+63   =B3 -> F8h=(2) B3667A2E for c3 c2 c1 c0
b3=f0+e0+d1+c2=78+23+5C+66=61 -> DEh=(1) 616BFFD3 for b3 b2 b1 b0

Now we have all needed values, then
X=(1)+         a0=         DE+66=B8
Y=(2)+      b0+a1=      F8+D3+EF=C4
Z=(3)+   c0+b1+a2=   4F+2E+FF+CD=53
W=(4)+d0+c1+b2+a3=35+01+7A+6B+AB=8E
(final computation)

结论:要将 CRC-32 的记存器的值从 ABCDEF66 改变到 56331478 我们需要这样一个字节
序列: B8 C4 53 8E

使用特权

评论回复
7
Massif123|  楼主 | 2010-2-3 12:44 | 只看该作者
CRC-32的**算法

  假如你考虑手动计算这个可以还原CRC记存器的字节序列,那么这将很难变成一个
简洁的算法.   
   
看看下面这个最后计算的附加版本:
                            Position
X =(1) +                a0     0
Y =(2) +           b0 + a1     1
Z =(3) +      c0 + b1 + a2     2
W =(4) + d0 + c1 + b2 + a3     3
f0= e0 + d1 + c2 + b3          4
f1= e1 + d2 + c3               5
f2= e2 + d3                    6
f3= e3                         7

(figure 5)
   
  它就等同于figure 4,只不过是一些值/字节被交换了.这种方法可以帮助我们构造一个
简洁的算法.这里我们用一个8字节的缓冲区,0-3位我们放置a0到a3,4-7位我们放置f0到
f3.象以前一样,我们用这个已知值e3(由figure 5中得知)在表中查出(e3 e2 e1 e0),并且
象图5(figure 5)中所示,将它们放到第4位(position 4),我们马上得到了d3的值.因为f2=
e2+d3,所以f2+e2=d3.又因为(4)已知(入口值),我们照样把它也放到位置3.然后在用d3查表
得到(d3 d2 d1 d0),同上也将他们放到图中所述位置.同样,由于有f1+e1+d2=c3在位置5上.
  我们继续做直到将b3 b2 b1 b0放到位置1,对了,就是它!  Et voila!
此时,缓冲区的第3-第0字节中已经包含全部元素,用来计算X~W!   

算法总结如下:
1.对于这个8字节的缓冲区,0~3字节放入a0...a3(CRC记存器起始值),4~7字节放入f0...f3
  (目标记存器的值).
2.取出位置7的已知值,查表得到相应值.
3.将查出值放如图5相应位置,其实就是做XOR运算.(为了直观,可以拟定此图)
4.将入口字节放入图中.也是做XOR运算.
5.继续做2,3两步3次,同时每次降低1个位置 position 5 to 4, 4 to 3 and so on.


算法的实现:

  现在是时候给出代码了.下面就是用汇编写成的可执行的CRC-32算法(用其他语言也一样
简单,对于其他的CRC-32标准也一样).注意在汇编中(计算机里)双字在读写操作中顺序都是
反着的.就是逆向顺序.
crcBefore       dd (?)
wantedCrc       dd (?)
buffer          db 8 dup (?)

        mov     eax, dword ptr[crcBefore] ;/*
        mov     dword ptr[buffer], eax
        mov     eax, dword ptr[wantedCrc] ; Step 1
        mov     dword ptr[buffer+4], eax  ;*/

        mov     di, 4
computeReverseLoop:
        mov     al, byte ptr[buffer+di+3] ;/*
        call   GetTableEntry              ; Step 2 */
        xor     dword ptr[buffer+di], eax ; Step 3
        xor     byte ptr[buffer+di-1], bl ; Step 4
        dec     di                        ;/*
        jnz    computeReverseLoop         ; Step 5 */

Notes:
-Registers eax, di bx are used

Implementation of GetTableEntry

crctable        dd 256 dup (?)       ;should be defined globally somewhere & initialized of course

        mov     bx, offset crctable-1
getTableEntryLoop:
        add     bx, 4                ;points to (crctable-1)+k*4 (k:1..256)
        cmp     [bx], al             ;must always find the value somewhere
        jne     getTableEntryLoop

        sub     bx, 3
        mov     eax, [bx]
        sub     bx, offset crctable
        shr     bx, 2

        ret

On return eax contains a table entry, bx contains the entry number.


Outtro

  好了...你终于读到了本文的结尾.假如你认为从此不管对什么样的CRC保护都可以说bye
bye了,那么你错了,不是的!很容易就可以写出对付**CRC的代码的.想要成功的**CRC
你需要知道在一个保护中,到底使用的是那一种CRC算法,并且要知道CRC的具体的计算位置.
比如说这里一种简单的对策就是使用2种不同的CRC算法,或者可以结合其他的数据保护算法
共同使用.
  无论如何...我希望所有这里所介绍的内容都是受人关注的,并且我希望你(读者)可以很
高兴的读着篇**,就象我很高兴写一样.

使用特权

评论回复
8
Massif123|  楼主 | 2010-2-3 12:45 | 只看该作者
附录:

CRC-16 Table

  00h   0000 C0C1 C181 0140 C301 03C0 0280 C241
  08h   C601 06C0 0780 C741 0500 C5C1 C481 0440
  10h   CC01 0CC0 0D80 CD41 0F00 CFC1 CE81 0E40
  18h   0A00 CAC1 CB81 0B40 C901 09C0 0880 C841

  20h   D801 18C0 1980 D941 1B00 DBC1 DA81 1A40
  28h   1E00 DEC1 DF81 1F40 DD01 1DC0 1C80 DC41
  30h   1400 D4C1 D581 1540 D701 17C0 1680 D641
  38h   D201 12C0 1380 D341 1100 D1C1 D081 1040

  40h   F001 30C0 3180 F141 3300 F3C1 F281 3240
  48h   3600 F6C1 F781 3740 F501 35C0 3480 F441
  50h   3C00 FCC1 FD81 3D40 FF01 3FC0 3E80 FE41
  58h   FA01 3AC0 3B80 FB41 3900 F9C1 F881 3840

  60h   2800 E8C1 E981 2940 EB01 2BC0 2A80 EA41
  68h   EE01 2EC0 2F80 EF41 2D00 EDC1 EC81 2C40
  70h   E401 24C0 2580 E541 2700 E7C1 E681 2640
  78h   2200 E2C1 E381 2340 E101 21C0 2080 E041

  80h   A001 60C0 6180 A141 6300 A3C1 A281 6240
  88h   6600 A6C1 A781 6740 A501 65C0 6480 A441
  90h   6C00 ACC1 AD81 6D40 AF01 6FC0 6E80 AE41
  98h   AA01 6AC0 6B80 AB41 6900 A9C1 A881 6840

  A0h   7800 B8C1 B981 7940 BB01 7BC0 7A80 BA41
  A8h   BE01 7EC0 7F80 BF41 7D00 BDC1 BC81 7C40
  B0h   B401 74C0 7580 B541 7700 B7C1 B681 7640
  B8h   7200 B2C1 B381 7340 B101 71C0 7080 B041

  C0h   5000 90C1 9181 5140 9301 53C0 5280 9241
  C8h   9601 56C0 5780 9741 5500 95C1 9481 5440
  D0h   9C01 5CC0 5D80 9D41 5F00 9FC1 9E81 5E40
  D8h   5A00 9AC1 9B81 5B40 9901 59C0 5880 9841

  E0h   8801 48C0 4980 8941 4B00 8BC1 8A81 4A40
  E8h   4E00 8EC1 8F81 4F40 8D01 4DC0 4C80 8C41
  F0h   4400 84C1 8581 4540 8701 47C0 4680 8641
  F8h   8201 42C0 4380 8341 4100 81C1 8081 4040


CRC-32 Table

  00h   00000000 77073096 EE0E612C 990951BA
  04h   076DC419 706AF48F E963A535 9E6495A3
  08h   0EDB8832 79DCB8A4 E0D5E91E 97D2D988
  0Ch   09B64C2B 7EB17CBD E7B82D07 90BF1D91

  10h   1DB71064 6AB020F2 F3B97148 84BE41DE
  14h   1ADAD47D 6DDDE4EB F4D4B551 83D385C7
  18h   136C9856 646BA8C0 FD62F97A 8A65C9EC
  1Ch   14015C4F 63066CD9 FA0F3D63 8D080DF5

  20h   3B6E20C8 4C69105E D56041E4 A2677172
  24h   3C03E4D1 4B04D447 D20D85FD A50AB56B
  28h   35B5A8FA 42B2986C DBBBC9D6 ACBCF940
  2Ch   32D86CE3 45DF5C75 DCD60DCF ABD13D59

  30h   26D930AC 51DE003A C8D75180 BFD06116
  34h   21B4F4B5 56B3C423 CFBA9599 B8BDA50F
  38h   2802B89E 5F058808 C60CD9B2 B10BE924
  3Ch   2F6F7C87 58684C11 C1611DAB B6662D3D

  40h   76DC4190 01DB7106 98D220BC EFD5102A
  44h   71B18589 06B6B51F 9FBFE4A5 E8B8D433
  48h   7807C9A2 0F00F934 9609A88E E10E9818
  4Ch   7F6A0DBB 086D3D2D 91646C97 E6635C01

  50h   6B6B51F4 1C6C6162 856530D8 F262004E
  54h   6C0695ED 1B01A57B 8208F4C1 F50FC457
  58h   65B0D9C6 12B7E950 8BBEB8EA FCB9887C
  5Ch   62DD1DDF 15DA2D49 8CD37CF3 FBD44C65

  60h   4DB26158 3AB551CE A3BC0074 D4BB30E2
  64h   4ADFA541 3DD895D7 A4D1C46D D3D6F4FB
  68h   4369E96A 346ED9FC AD678846 DA60B8D0
  6Ch   44042D73 33031DE5 AA0A4C5F DD0D7CC9

  70h   5005713C 270241AA BE0B1010 C90C2086
  74h   5768B525 206F85B3 B966D409 CE61E49F
  78h   5EDEF90E 29D9C998 B0D09822 C7D7A8B4
  7Ch   59B33D17 2EB40D81 B7BD5C3B C0BA6CAD

  80h   EDB88320 9ABFB3B6 03B6E20C 74B1D29A
  84h   EAD54739 9DD277AF 04DB2615 73DC1683
  88h   E3630B12 94643B84 0D6D6A3E 7A6A5AA8
  8Ch   E40ECF0B 9309FF9D 0A00AE27 7D079EB1

  90h   F00F9344 8708A3D2 1E01F268 6906C2FE
  94h   F762575D 806567CB 196C3671 6E6B06E7
  98h   FED41B76 89D32BE0 10DA7A5A 67DD4ACC
  9Ch   F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5

  A0h   D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252
  A4h   D1BB67F1 A6BC5767 3FB506DD 48B2364B
  A8h   D80D2BDA AF0A1B4C 36034AF6 41047A60
  ACh   DF60EFC3 A867DF55 316E8EEF 4669BE79

  B0h   CB61B38C BC66831A 256FD2A0 5268E236
  B4h   CC0C7795 BB0B4703 220216B9 5505262F
  B8h   C5BA3BBE B2BD0B28 2BB45A92 5CB36A04
  BCh   C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D

  C0h   9B64C2B0 EC63F226 756AA39C 026D930A
  C4h   9C0906A9 EB0E363F 72076785 05005713
  C8h   95BF4A82 E2B87A14 7BB12BAE 0CB61B38
  CCh   92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21

  D0h   86D3D2D4 F1D4E242 68DDB3F8 1FDA836E
  D4h   81BE16CD F6B9265B 6FB077E1 18B74777
  D8h   88085AE6 FF0F6A70 66063BCA 11010B5C
  DCh   8F659EFF F862AE69 616BFFD3 166CCF45

  E0h   A00AE278 D70DD2EE 4E048354 3903B3C2
  E4h   A7672661 D06016F7 4969474D 3E6E77DB
  E8h   AED16A4A D9D65ADC 40DF0B66 37D83BF0
  ECh   A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9

  F0h   BDBDF21C CABAC28A 53B39330 24B4A3A6
  F4h   BAD03605 CDD70693 54DE5729 23D967BF
  F8h   B3667A2E C4614AB8 5D681B02 2A6F2B94
  FCh   B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D

  

CRC算法原理及C語言實現

对于RF通讯的通讯可靠性,有很强的检错能力的CRC.
这里列出了实际中8,16位单片机用到的CRC实用子程序.  

1. 半字节16位CRC---halfBcal_crc  
2.查表CRC---Bytecal_crc  
3.位计算的CRC.----bitcal_crc  
4.CRC检错的程序----IsCrc16  
5.一个很不错的CRC计算程序,--CRC16  


同时本文列出了调用函数和例程  

const code Ploy=0x1021;  

#ifndef uchar  
typedef unsigned char uchar;  
#endif  
#ifndef uint  
typedef unsigned int uint;  
#endif  
static unsigned short crc; //16bit  
unsigned int code crc_ta[16]={ /* CRC 半字节余式表 */ 0X0000,0X1021,0X2042, 0X3063, 0X4084, 0X50A5, 0X60C6, 0X70E7, 0X8108,0X9129,0XA14A,0XB16B,0XC18C,0XD1AD,0XE1CE,0XF1EF, };  



unsigned int code crc_tab[256]={ /* CRC 余式表 */ 0X0000, 0X1021, 0X2042, 0X3063, 0X4084, 0X50A5, 0X60C6, 0X70E7, 0X8108, 0X9129, 0XA14A, 0XB16B, 0XC18C, 0XD1AD, 0XE1CE, 0XF1EF, 0X1231, 0X0210, 0X3273, 0X2252, 0X52B5, 0X4294, 0X72F7, 0X62D6, 0X9339, 0X8318, 0XB37B, 0XA35A, 0XD3BD, 0XC39C, 0XF3FF, 0XE3DE, 0X2462, 0X3443, 0X0420, 0X1401, 0X64E6, 0X74C7, 0X44A4, 0X5485, 0XA56A,0XB54B, 0X8528, 0X9509, 0XE5EE, 0XF5CF, 0XC5AC, 0XD58D, 0X3653, 0X2672, 0X1611, 0X0630, 0X76D7, 0X66F6, 0X5695, 0X46B4, 0XB75B,0XA77A, 0X9719, 0X8738, 0XF7DF, 0XE7FE, 0XD79D, 0XC7BC, 0X48C4,0X58E5, 0X6886, 0X78A7, 0X0840, 0X1861, 0X2802, 0X3823, 0XC9CC, 0XD9ED, 0XE98E, 0XF9AF, 0X8948, 0X9969, 0XA90A, 0XB92B, 0X5AF5, 0X4AD4, 0X7AB7, 0X6A96, 0X1A71, 0X0A50, 0X3A33, 0X2A12, 0XDBFD, 0XCBDC, 0XFBBF, 0XEB9E, 0X9B79, 0X8B58, 0XBB3B, 0XAB1A, 0X6CA6, 0X7C87, 0X4CE4, 0X5CC5, 0X2C22, 0X3C03, 0X0C60, 0X1C41, 0XEDAE, 0XFD8F, 0XCDEC, 0XDDCD, 0XAD2A, 0XBD0B, 0X8D68, 0X9D49, 0X7E97, 0X6EB6, 0X5ED5, 0X4EF4, 0X3E13, 0X2E32, 0X1E51, 0X0E70, 0XFF9F, 0XEFBE, 0XDFDD, 0XCFFC, 0XBF1B, 0XAF3A, 0X9F59, 0X8F78, 0X9188, 0X81A9, 0XB1CA, 0XA1EB, 0XD10C, 0XC12D, 0XF14E, 0XE16F, 0X1080, 0X00A1, 0X30C2, 0X20E3, 0X5004, 0X4025, 0X7046, 0X6067, 0X83B9, 0X9398, 0XA3FB, 0XB3DA, 0XC33D, 0XD31C, 0XE37F, 0XF35E, 0X02B1, 0X1290, 0X22F3, 0X32D2, 0X4235, 0X5214, 0X6277, 0X7256, 0XB5EA, 0XA5CB, 0X95A8, 0X8589, 0XF56E, 0XE54F, 0XD52C, 0XC50D, 0X34E2, 0X24C3, 0X14A0, 0X0481, 0X7466, 0X6447, 0X5424, 0X4405, 0XA7DB, 0XB7FA, 0X8799, 0X97B8, 0XE75F, 0XF77E, 0XC71D, 0XD73C, 0X26D3, 0X36F2, 0X0691, 0X16B0, 0X6657, 0X7676, 0X4615, 0X5634, 0XD94C, 0XC96D, 0XF90E, 0XE92F, 0X99C8, 0X89E9, 0XB98A, 0XA9AB, 0X5844, 0X4865, 0X7806, 0X6827, 0X18C0, 0X08E1, 0X3882, 0X28A3, 0XCB7D, 0XDB5C, 0XEB3F, 0XFB1E, 0X8BF9, 0X9BD8, 0XABBB, 0XBB9A, 0X4A75, 0X5A54, 0X6A37, 0X7A16, 0X0AF1, 0X1AD0, 0X2AB3, 0X3A92, 0XFD2E, 0XED0F, 0XDD6C, 0XCD4D, 0XBDAA, 0XAD8B, 0X9DE8, 0X8DC9, 0X7C26, 0X6C07, 0X5C64, 0X4C45, 0X3CA2, 0X2C83, 0X1CE0, 0X0CC1, 0XEF1F, 0XFF3E, 0XCF5D, 0XDF7C, 0XAF9B, 0XBFBA, 0X8FD9, 0X9FF8, 0X6E17, 0X7E36, 0X4E55, 0X5E74, 0X2E93, 0X3EB2, 0X0ED1, 0X1EF0 };  

unsigned int halfBcal_crc(unsigned char *ptr, unsigned char len)
{
unsigned int crc;
unsigned char da;  
crc=0;
while(len--!=0)
{  
// da=((uchar)(crc/256))/16; /* 暂存CRC 的高四位 */
da=((unsigned char)(crc>>8))>>4;  
crc<<=4; /* CRC 右移4 位,相当于取CRC 的低12 位)*/
crc^=crc_ta[da^(*ptr/16)]; /* CRC 的高4 位和本字节的前半字节相加后查表计算CRC, 然后加上上一次CRC 的余数 */
da=((unsigned char)(crc>>8))>>4; /* 暂存CRC 的高4 位 */
crc<<=4; /* CRC 右移4 位, 相当于CRC 的低12 位) */
crc^=crc_ta[da^(*ptr&0x0f)]; /* CRC 的高4 位和本字节的后半字节相加后查表计算CRCH缓笤偌由仙弦淮蜟RC 的余数 */  

ptr++; }

return(crc);

}  

unsigned int bitcal_crc(unsigned char *ptr, unsigned char len)


{ unsigned char i; unsigned int crc=0; while(len--!=0)  

{  

for(i=0x80; i!=0; i/=2)  

{  

if((crc&0x8000)!=0)  

{

crc<<=1;

crc^=0x1021;

} /* 余式CRC 乘以2 再求CRC */  

else crc<<=1;

if((*ptr&i)!=0) crc^=Ploy; /* 再加上本位的CRC */ }

ptr++;  

}  

return(crc);

}

unsigned int Bytecal_crc(unsigned char *ptr, unsigned char len)  

{  

unsigned int crc;  

unsigned char da;

crc=0;  

while(len--!=0)

{  

da=(uchar) (crc/256); /* 以8 位二进制数的形式暂存CRC 的高8 位 */  

crc<<=8; /* 左移8 位,相当于CRC 的低8 位乘以8 */

crc^=crc_tab[da^*ptr]; /* 高8 位和当前字节相加后再查表求CRC ,再加上以前的CRC */

ptr++;

}

return(crc);

}

uchar IsCrc16(const uchar *pData,int len)  

{ uint fcs;  

fcs=0xffff;

while(len>0)  

{

fcs = (fcs << 8)^ crc_tab[fcs^ (*pData)&0xff];  

len--;

pData++;

}

return(!fcs);

}

unsigned char CRC16(unsigned char ser_data)  

{ // Update the CRC for transmitted and received data using // the CCITT 16bit algorithm (X^16 + X^12 + X^5 + 1). crc = (unsigned char)(crc >> 8) | (crc << 8);

crc ^= ser_data;  

crc ^= (unsigned char)(crc & 0xff) >> 4;

crc ^= (crc << 8) << 4; crc ^= ((crc & 0xff) << 4) << 1;  

return 0;  

}

unsigned short doCRC16( unsigned char *mstr,unsigned char len)  

{  

unsigned char ch;  

crc = 0;  

do{  

ch = *mstr++;  

CRC16(ch);

}while(--len);  

return(crc);

}

使用特权

评论回复
9
Massif123|  楼主 | 2010-2-3 12:46 | 只看该作者
CRC-16/CRC-32 程序代码:
不久前写一程序时要用到 CRC-16 ,但找来找去只在 UDDF 里找到一个 Delphi 的 CRC-32 程序代码,而且是用查表法,虽然说查表法速度快,但 256 项 32 位数据我怀疑可能会有输入错误, 让人不是那么放心,而我又不知道这个表是怎么算出来的。后来我又在一本两年前的笔记本里找到一段关于 CRC 的内容, 也不知是从哪里抄来的,还好里面有一段程序代码,是 CRC-16 的,这段程序正是产生 CRC 表的, 可是这区区几行的程序(基本上与下面的 BuilderTable16 函数相同)看得我一头雾水,直到这两天才弄明白, 并据此推出 CRC-32 的算法,现将全部程序列在下面,并作一些说明以助于理解,不但要知其然,还要知其所以然嘛:  

// 注意:因最高位一定为“1”,故略去  
const unsigned short cnCRC_16 = 0x8005;  
// CRC-16 = X16 + X15 + X2 + X0  
const unsigned short cnCRC_CCITT = 0x1021;  
// CRC-CCITT = X16 + X12 + X5 + X0,据说这个 16 位 CRC 多项式比上一个要好  
const unsigned long cnCRC_32 = 0x04C10DB7;  
// CRC-32 = X32 + X26 + X23 + X22 + X16 + X11 + X10 + X8 + X7 + X5 + X4 + X2 + X1 + X0  

unsigned long Table_CRC[256]; // CRC 表  

// 构造 16 位 CRC 表  
void BuildTable16( unsigned short aPoly )  
{  
unsigned short i, j;  
unsigned short nData;  
unsigned short nAccum;  

for ( i = 0; i < 256; i++ )  
{  
nData = ( unsigned short )( i << 8 );  
nAccum = 0;  
for ( j = 0; j < 8; j++ )  
{  
if ( ( nData ^ nAccum ) & 0x8000 )  
nAccum = ( nAccum << 1 ) ^ aPoly;  
else  
nAccum <<= 1;  
nData <<= 1;  
}  
Table_CRC[i] = ( unsigned long )nAccum;  
}  
}  

// 计算 16 位 CRC 值,CRC-16 或 CRC-CCITT  
unsigned short CRC_16( unsigned char * aData, unsigned long aSize )  
{  
unsigned long i;  
unsigned short nAccum = 0;  

BuildTable16( cnCRC_16 ); // or cnCRC_CCITT  
for ( i = 0; i < aSize; i++ )  
nAccum = ( nAccum << 8 ) ^ ( unsigned short )Table_CRC[( nAccum >> 8 ) ^ *aData++];  
return nAccum;  
}  

// 构造 32 位 CRC 表  
void BuildTable32( unsigned long aPoly )  
{  
unsigned long i, j;  
unsigned long nData;  
unsigned long nAccum;  

for ( i = 0; i < 256; i++ )  
{  
nData = ( unsigned long )( i << 24 );  
nAccum = 0;  
for ( j = 0; j < 8; j++ )  
{  
if ( ( nData ^ nAccum ) & 0x80000000 )  
nAccum = ( nAccum << 1 ) ^ aPoly;  
else  
nAccum <<= 1;  
nData <<= 1;  
}  
Table_CRC[i] = nAccum;  
}  
}  

// 计算 32 位 CRC-32 值  
unsigned long CRC_32( unsigned char * aData, unsigned long aSize )  
{  
unsigned long i;  
unsigned long nAccum = 0;  

BuildTable32( cnCRC_32 );  
for ( i = 0; i < aSize; i++ )  
nAccum = ( nAccum << 8 ) ^ Table_CRC[( nAccum >> 24 ) ^ *aData++];  
return nAccum;  
}  

说明: CRC 的计算原理如下(一个字节的简单例子)  
11011000 00000000 00000000 <- 一个字节数据, 左移 16b  
^10001000 00010000 1 <- CRC-CCITT 多项式, 17b  
--------------------------  
1010000 00010000 10 <- 中间余数  
^1000100 00001000 01  
-------------------------  
10100 00011000 1100  
^10001 00000010 0001  
-----------------------  
101 00011010 110100  
^100 01000000 100001  
---------------------  
1 01011010 01010100  
^1 00010000 00100001  
-------------------  
01001010 01110101 <- 16b CRC  

仿此,可推出两个字节数据计算如下:d 为数据,p 为项式,a 为余数  
dddddddd dddddddd 00000000 00000000 <- 数据 D ( D1, D0, 0, 0 )  
^pppppppp pppppppp p <- 多项式 P  
-----------------------------------  
...  
aaaaaaaa aaaaaaaa 0 <- 第一次的余数 A'' ( A''1, A''0 )  
^pppppppp pppppppp p  
--------------------------  
...  
aaaaaaaa aaaaaaaa <- 结果 A ( A1, A0 )  

由此与一字节的情况比较,将两个字节分开计算如下:  
先算高字节:  
dddddddd 00000000 00000000 00000000 <- D1, 0, 0, 0  
^pppppppp pppppppp p <- P  
-----------------------------------  
...  
aaaaaaaa aaaaaaaa <- 高字节部分余数 PHA1, PHA0  

此处的部分余数与前面两字节算法中的第一次余数有如下关系,即 A''1 = PHA1 ^ D0, A''0 = PHA0:  
aaaaaaaa aaaaaaaa <- PHA1, PHA0  
^dddddddd <- D0  
-----------------  
aaaaaaaa aaaaaaaa <- A''1, A''0  

低字节的计算:  
aaaaaaaa 00000000 00000000 <- A''1, 0, 0  
^pppppppp pppppppp p <- P  
--------------------------  
...  
aaaaaaaa aaaaaaaa <- 低字节部分余数 PLA1, PLA0  
^aaaaaaaa <- A''0 , 即 PHA0  
-----------------  
aaaaaaaa aaaaaaaa <- 最后的 CRC ( A1, A0 )  

总结以上内容可得规律如下:  
设部分余数函数  
PA = f( d )  
其中 d 为一个字节的数据(注意,除非 n = 0 ,否则就不是原始数据,见下文)  
第 n 次的部分余数  
PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( d )  
其中的  
d = ( PA( n - 1 ) >> 8 ) ^ D( n )  
其中的 D( n ) 才是一个字节的原始数据。  

公式如下:  
PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( ( PA( n - 1 ) >> 8 ) ^ D( n ) )  

可以注意到函数 f( d ) 的参数 d 为一个字节,对一个确定的多项式 P, f( d ) 的返回值  
是与 d 一一对应的,总数为 256 项,将这些数据预先算出保存在表里,f( d )就转换为一  
个查表的过程,速度也就可以大幅提高,这也就是查表法计算 CRC 的原理,在 CRC_16 和  
CRC_32 两个函数的循环中的语句便是上面那个公式。  

再来看 CRC 表是如何计算出来的,即函数 f( d ) 的实现方法。分析前面一个字节数据的  
计算过程可发现,d 对结果的影响只表现为对 P 的移位异或,看计算过程中的三个 8 位  
的列中只有低两个字节的最后结果是余数,而数据所在的高 8 位列最后都被消去了,因其  
中的运算均为异或,不产生进位或借位,故每一位数据只影响本列的结果,即 d 并不直接  
影响结果。再将前例变化一下重列如下:  
11011000  
--------------------------  
10001000 00010000 1 // P  
^ 1000100 00001000 01 // P  
^ 000000 00000000 000 // 0  
^ 10001 00000010 0001 // P  
^ 0000 00000000 00000 // 0  
^ 100 01000000 100001 // P  
^ 00 00000000 0000000 // 0  
^ 1 00010000 00100001 // P  
-------------------  
01001010 01110101  

现在的问题就是如何根据 d 来对 P 移位异或了,从上面的例子看,也可以理解为每步  
移位,但根据 d 决定中间余数是否与 P 异或。从前面原来的例子可以看出,决定的条  
件是中间余数的最高位为0,因为 P 的最高位一定为1,即当中间余数与 d 相应位异或  
的最高位为1时,中间余数移位就要和 P 异或,否则只需移位即可。具体做法见程序中  
的 BuildTable16 和 BuildTable32 两个函数,其方法如下例(上例的变形,注意其中  
空格的移动表现了 d 的影响如何被排除在结果之外):  

d --------a--------  
1 00000000 00000000 <- HSB = 1  
0000000 000000000 <- a <<= 1  
0001000 000100001 <- P, CRC-CCITT 不含最高位的 1  
-----------------  
1 0001000 000100001  
001000 0001000010  
000100 0000100001  
-----------------  
0 001100 0001100011 <- HSB = 0  
01100 00011000110  
-----------------  
1 01100 00011000110 <- HSB = 1  
1100 000110001100  
0001 000000100001  
-----------------  
1 1101 000110101101 <- HSB = 0  
101 0001101011010  
-----------------  
0 101 0001101011010 <- HSB = 1  
01 00011010110100  
00 01000000100001  
-----------------  
0 01 01011010010101 <- HSB = 0  
1 010110100101010  
-----------------  
0 1 010110100101010 <- HSB = 1  
0101101001010100  
0001000000100001  
-----------------  
0100101001110101 <- CRC  

结合这些,前面的程序就好理解了。至于 32 位 CRC 的计算与 16 相似,就不多加说明,请参考源程序。

使用特权

评论回复
10
yuechugxy| | 2010-2-3 13:18 | 只看该作者
不错!顶起来!

使用特权

评论回复
11
iammercy| | 2010-2-3 15:07 | 只看该作者
有沒有word檔或pdf檔,論壇上的數字公式沒對齊很難看得清晰

使用特权

评论回复
12
lhkjg| | 2010-2-3 18:03 | 只看该作者
同意楼上

使用特权

评论回复
13
utterboy| | 2010-2-3 20:54 | 只看该作者
mark

使用特权

评论回复
14
M.gtd| | 2010-2-4 08:50 | 只看该作者
学习ing

使用特权

评论回复
15
風神| | 2010-2-4 09:54 | 只看该作者
让老hot大树出马。。。不过老hot大树不玩科普这招。

使用特权

评论回复
16
ctunan| | 2010-2-5 13:57 | 只看该作者
不错,马克之~~~

使用特权

评论回复
17
linux1| | 2010-2-15 01:50 | 只看该作者
不错,谢谢楼主分享

使用特权

评论回复
18
myic200610| | 2010-2-15 02:02 | 只看该作者
谢谢楼主

使用特权

评论回复
19
on_raptor| | 2010-2-15 15:05 | 只看该作者
mark
二姨几时不让保存网页了?

使用特权

评论回复
20
S3C2440| | 2010-2-18 21:44 | 只看该作者
LZ真是辛苦了,好好的给我们上了一课啊!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

177

主题

276

帖子

1

粉丝