CRC校验算法原理分析

      CRC校验码的基本思想是利用线性编码理论, 在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的监督码(既CRC码)r位,并附在信息后边,构成一个新的二进制码序列数共(k+r)位,最后发送出去。在接收端,则根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错,在数据存储和数据通讯领域常用。

         生成多项式的选择是 CRC 算法实现中最重要的部分,所选择的多项式必须有最大的错误检测能力,同时保证总体的碰撞概率最小。多项式最重要的属性是它的长度,也就是最高非零系数的数值,因为它直接影响着计算的校验和的长度。

最常用的多项式长度有

  • 9 位 (CRC-8)
  • 17 位 (CRC-16)
  • 33 位 (CRC-32)
  • 65 位 (CRC-64)

         在构建一个新的 CRC 多项式或者改进现有的 CRC 时,一个通用的数学原则是使用满足所有模运算不可分解多项式约束条件的多项式。

  • 这种情况下的不可分解是指多项式除了 1 与它自身之外不能被任何其它的多项式整除。生成多项式的特性可以从算法的定义中推导出来:
  • 如果 CRC 有多于一个的非零系数,那么 CRC 能够检查出输入消息中的所有单数据位错误。
  • CRC 可以用于检测短于 2k 的输入消息中的所有双位错误,其中 k 是多项式的最长的不可分解部分的长度。
  • 如果多项式可以被 x+1 整除,那么不存在可以被它整除的有奇数个非零系数的多项式。因此,它可以用来检测输入消息中的奇数个错误,就象奇偶校验函数那样。


这里以CRC8作为介绍校验算法过程。

1、 CRC8标准生成多项式

CRC-8       x8+x5+x4+1              0x31(0x131)
CRC-8       x8+x2+x1+1              0x07(0x107)
CRC-8       x8+x6+x4+x3+x2+x1       0x5E(0x15E)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

注:由于多项式的最高为都为1,并且在代码的crc8计算中,最高位也是不使用的, 
所以在多项式记录时都去掉了最高位。

2、 CRC校验算法,说白了,就是把需要校验的数据与多项式进行循环异或(XOR), 但进行XOR的方式与实际中数据传输时,是高位先传、还是低位先传有关。对于数据 高位先传的方式,XOR从数据的高位开始,我们就叫它顺序异或吧;对于数据低位先 传的方式,XOR从数据的低位开始,我们就叫它反序异或吧。两种不同的异或方式, 即使对应相同的多项式,计算出来的结果也是不一样的。 

下面以顺序异或的例子说明一些计算的过程: 


使用多项式:x8+x5+x4+1(二进制为:100110001) 


计算一个字节:0x11(二进制为:00010001) 



计算步骤: 
A、 因为采用顺序异或,所以需要计算的数据左移8位, 
移位后数据为:0001 0001 0000 000 


B、 先进行高9bit异或(多项式为9bit),0001 0001 0000 0000,因为高9bit的 
最高bit为0,不需要进行异或,同理,接下来的两bit也是0,也不需要进行进行异或。 
这样处理后数据为:1 0001 0000 0000; 


C、 接下来最高位为1,需要进行异或操作了 
这里写图片描述 


从上面的计算过程可以看到,多项式最高位为1,遇到需要异或数据最高位为1时, 才进行异或计算,并且异或后,最高位就为0了,最高位为0,下次也不需要异或了, 这样需要采用代码计算的方式,就可以把最高位去掉,不需要异或,最后结果也是一样的。 对于上面的计算过程,采用代码实现的方式如下:

unsigned char cal_table_high_first(unsigned char value)
{
    unsigned char i, crc;

    crc = value;
    /* 数据往左移了8位,需要计算8次 */
    for (i=8; i>0; --i)
    { 
        if (crc & 0x80)  /* 判断最高位是否为1 */
        {
        /* 最高位为1,不需要异或,往左移一位,然后与0x31异或 */
        /* 0x31(多项式:x8+x5+x4+1,100110001),最高位不需要异或,直接去掉 */
            crc = (crc << 1) ^ 0x31;        }
        else
        {
            /* 最高位为0时,不需要异或,整体数据往左移一位 */
            crc = (crc << 1);
        }
    }

    return crc;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22字节的crc

     

      上面的代码计算一个字节的crc结果,如果计算多个字节的结果也是比较简单的,先计算第一个字节的crc结果,然后把第一个字节的crc结果与第二个字节进行异或, 异或后的值再进行一次crc计算就可以了,多个字节也是反复这过程就好,如下为多个字节 的crc校验代码:

unsigned char crc_high_first(unsigned char *ptr, unsigned char len)
{
    unsigned char i; 
    unsigned char crc=0x00/* 计算的初始crc值 */ 

    while(len--)
    {
        crc ^= *ptr++;  /* 每次先与需要计算的数据异或,计算完指向下一数据 */  
        for (i=8; i>0; --i)   /* 下面这段计算过程与计算一个字节crc一样 */  
        { 
            if (crc & 0x80)
                crc = (crc << 1) ^ 0x31;
            else
                crc = (crc << 1);
        }
    }

    return (crc); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19rc计算是纯采用逻辑运行的方式,可以看到,需要的运行量也是不少的,每一个字节


     上面的crc计算是纯采用逻辑运行的方式,可以看到,需要的运行量也是不少的,每一个字节都需要进行8次判断、移位、或异或操作。可以采用查表法,大大减少计算量,先计算出 0x00~0xFF每一个字节的crc校验结果,后面就可以通过表来查出每个字节的crc结果,大大 减少计算量。 下面是一个表生成程序: 

(生成表对应多项式:0x31(多项式:x8+x5+x4+1,100110001))

    void  create_crc_table(void)
    {
        unsigned short i;
        unsigned char j;

        for (i=0; i<=0xFF; i++)
        {
            if (0 == (i%16))
                printf("\n");

            j = i&0xFF;
            printf("0x%.2x, ", cal_table_high_first (j));  /*依次计算每个字节的crc校验值*/
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

得到的表整理后如下:

static const unsigned char crc_table[] =
{
    0x00,0x31,0x62,0x53,0xc4,0xf5,0xa6,0x97,0xb9,0x88,0xdb,0xea,0x7d,0x4c,0x1f,0x2e,
    0x43,0x72,0x21,0x10,0x87,0xb6,0xe5,0xd4,0xfa,0xcb,0x98,0xa9,0x3e,0x0f,0x5c,0x6d,
    0x86,0xb7,0xe4,0xd5,0x42,0x73,0x20,0x11,0x3f,0x0e,0x5d,0x6c,0xfb,0xca,0x99,0xa8,
    0xc5,0xf4,0xa7,0x96,0x01,0x30,0x63,0x52,0x7c,0x4d,0x1e,0x2f,0xb8,0x89,0xda,0xeb,
    0x3d,0x0c,0x5f,0x6e,0xf9,0xc8,0x9b,0xaa,0x84,0xb5,0xe6,0xd7,0x40,0x71,0x22,0x13,
    0x7e,0x4f,0x1c,0x2d,0xba,0x8b,0xd8,0xe9,0xc7,0xf6,0xa5,0x94,0x03,0x32,0x61,0x50,
    0xbb,0x8a,0xd9,0xe8,0x7f,0x4e,0x1d,0x2c,0x02,0x33,0x60,0x51,0xc6,0xf7,0xa4,0x95,
    0xf8,0xc9,0x9a,0xab,0x3c,0x0d,0x5e,0x6f,0x41,0x70,0x23,0x12,0x85,0xb4,0xe7,0xd6,
    0x7a,0x4b,0x18,0x29,0xbe,0x8f,0xdc,0xed,0xc3,0xf2,0xa1,0x90,0x07,0x36,0x65,0x54,
    0x39,0x08,0x5b,0x6a,0xfd,0xcc,0x9f,0xae,0x80,0xb1,0xe2,0xd3,0x44,0x75,0x26,0x17,
    0xfc,0xcd,0x9e,0xaf,0x38,0x09,0x5a,0x6b,0x45,0x74,0x27,0x16,0x81,0xb0,0xe3,0xd2,
    0xbf,0x8e,0xdd,0xec,0x7b,0x4a,0x19,0x28,0x06,0x37,0x64,0x55,0xc2,0xf3,0xa0,0x91,
    0x47,0x76,0x25,0x14,0x83,0xb2,0xe1,0xd0,0xfe,0xcf,0x9c,0xad,0x3a,0x0b,0x58,0x69,
    0x04,0x35,0x66,0x57,0xc0,0xf1,0xa2,0x93,0xbd,0x8c,0xdf,0xee,0x79,0x48,0x1b,0x2a,
    0xc1,0xf0,0xa3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1a,0x2b,0xbc,0x8d,0xde,0xef,
    0x82,0xb3,0xe0,0xd1,0x46,0x77,0x24,0x15,0x3b,0x0a,0x59,0x68,0xff,0xce,0x9d,0xac
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
采用查表法计算crc代码如下:
unsigned char cal_crc_table(unsigned char *ptr, unsigned char len) 
{
    unsigned char  crc = 0x00;

    while (len--)
    {
        crc = crc_table[crc ^ *ptr++];
    }
    return (crc);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10



3、 反序异或的计算 
反序异或与顺序异或差异在数据先判断最低位,并且数据是向右移 的,并且多项式数据位需要高低位反转一下。 
还是以多项式:x8+x5+x4+1(二进制为:100110001)为例: 则计算一个字节的crc校验代码如下:

unsigned char cal_table_low_first(unsigned char value)
{
    unsigned char i, crc;

    crc = value;
/* 同样需要计算8次 */
    for (i=8; i>0; --i)
    { 
        if (crc & 0x01)  /* 反序异或变成判断最低位是否为1 */
            /* 数据变成往右移位了 */
            /* 计算的多项式从0x31(0011 0001)变成了0x8C (1000 1100) */
/* 多项式值,原来的最高位变成了最低位,原来的最低位变成最高位,8位数据高低位交换一下位置 */
            crc = (crc >> 1) ^ 0x8C;
        else
            crc = (crc >> 1);
    }

    return crc;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

至于多个字节的crc校验及crc数据表的生成,只要把单个字节的计算方式替换一下顺序的 计算方式即可,这里就不再列出。所以,只要明确了crc校验使用的多项式,高位先校验 还是低位先校验,计算crc的初始值是什么,那crc的计算就变得很简单了。

你可能感兴趣的:(硬件信息传输安全加密算法,嵌入式软件设计)