CRC原理及實現

epluguo發表於2013-08-20

CRC(Cyclic Redundancy Check)中文名是迴圈冗餘校驗,由於計算簡單等,被廣泛用於資料校驗,具有很強的檢錯能力。最常見的有網路傳輸中資訊的校驗,但同樣的道理,我們也可以將其應用到軟體中,如:winrar對檔案的校驗。另外,我們還可以將它應用到軟體上面來保護軟體被惡意修改,還可以用在記憶體動態校驗上面。用途多多,變通方式也不一,但基本原理都是這個。

CRC演算法的是以GF(2)(2元素伽羅瓦域)多項式算術為數學基礎的,嗯,聽著是有點恐怖,但別被嚇著了,其實說白了也很簡單。雖然我們大可跳過數學部分用計算機的術語來描述,但我依舊不想跳過這部分,以防後來者忽略數學在計算機中的重要性,能看到數學在計算機的應用也能鼓舞起他們學習數學的信心。無可置疑,很多演算法都源於數學,都以數學為支撐,數學功底在一定程度上決定你能走到的高度。

首先,我們來介紹一下2元素伽羅瓦域及其基本運算。

GF(2)多項式中只有一個變數x,其係數也只有0和1,如:

1*x^6 + 1*x^5 + 0*x^4 + 0*x^3 + 1*x^2 +1*x^1 + 1*x^0
即:
x^6 + x^5 + x^2 + x + 1
(x^n表示x的n次冪)   

GF(2)的多項式加減運算其實都一樣:模2運算,運算時不考慮借位和進位。咋一聽好像很高階似的,其實說白了就是異或運算。


0 + 0 = 0    0 - 0 = 0
0 + 1 = 1    0 - 1 = 1 
1 + 0 = 1    1 - 0 = 1
1 + 1 = 0    1 - 1 = 0

比如:P1 = x^6 +x^5 + x^2 + x + 1,P2= x^3+ x^2 + 1。P1 + P2 = x^6+ x^5 + x^3+ x。即對應項想加減。

GF(2)的多項式乘除運算也與一般的多項式大體一樣,只是加減時進行的是模2運算。不再詳述,待會看下去就行。

CRC的數學基礎就說到這裡吧。再說下去只會讓大家都懵了,我們還是換一些直白點的語言來描述下CRC原理。

在小學我們就知道,乘法可以通過一直加某個數來得到結果,同樣除法也可以通過一直減某個數來得到結果。看下下面兩個式子:

9/3=3 (餘數是0),即9 – 3 – 3 –3 = 0

9/6 = 1 (餘數是3),即9 – 6 = 3

用二進位制來表示就是:

1001			-->9
0011	-		-->3
------------------------------------------------
0110			-->6
0011	-		-->3
------------------------------------------------
0011			-->3
0011	-		-->3
------------------------------------------------
0000			-->餘數

減了三次,顯然商是3,而最後剛好減完,餘數為0。

再來看下另外一個:

1001			-->9
0110	-		-->6
------------------------------------------------
0011			-->3 , 餘數

這次商是1,餘數是3。

很容易吧。但上面的是有借位的,並不是我們之前說的2元素伽羅瓦域的減法。我們再來看看2元素伽羅瓦域的除法,也即CRC的運算。

如果忽略借位,那8/3 =?……?


1000			-->8
11  	-		-->3
------------------------------------------------
 100			-->4
 11  	-		-->3
------------------------------------------------
  10			-->2
  11 	-		-->3
------------------------------------------------
   1			-->1 , 餘數  ……The CRC

好奇怪,是吧,為什麼從高位開始減呢?我們還原成除法就不奇怪了。


     111        -->商
   ----------
11)1000
    11
-------------------
     100
     11
--------------------
      10
      11
--------------------
       1        -->餘數,The CRC

普通的除法可以還原為熟悉的減法,但2元素伽羅瓦域的除法記得注意點了。從高位開始減。另外,餘數得比除數的字長少,以二進位制來準。不懂?11的字長是2,1的字長是1,這樣懂了吧。而餘數就是我們所關心的CRC。

進行一個CRC運算我們需要選擇一個除數,這個除數我們叫它為“poly”(生成項),寬度W就是最高位的位置,所以我剛才舉的例子中的除數3,這個poly 11的W是1,而不是2。注意最高位總是1,就像普通的數字小數點前面的0可以忽略一樣。這裡還要注意寬度與字長的區別,字長是從1開始計算的,而寬度是從0開始計算的。

唔~~,還有個問題,我們想確保每個位都能被處理一遍該怎麼做?畢竟我們不能忽略某個部分,這樣校驗就不完全了。很簡單,我們在要處理的目標位串後面加上W個0就行了。

現在我們再來看看改進後的CRC除法。

Poly                          =    1001,寬度W = 3
位串Bitstring            =    11110
Bitstring + W zeroes    =    11110 + 000 = 11110000

11110000
1001||||    -
-------------
 1100|||
 1001|||    -
 ------------
  1010||
  1001||    -
  -----------
   0110|
   0000|    -
   ----------
    1100
    1001    -
    ---------
     101        --> 5,餘數 --> The CRC!

最後,還有兩點要注意一下。咋這麼多注意啊,別急,我們慢慢來。

1、只有當Bitstring的最高位為1,我們才將它與Poly進行XOR運算,否則我們只是將Bitstring左移一位。其實這很好理解,想下除法運算就明白了,這裡只是用簡單的減法來代替而已。

2、XOR運算的結果就是被操作位串Bitstring與Poly的低W位進行XOR運算,因為最高位總為0。

好了,以上就是CRC的原理了,暈了?那就再看一遍吧。其實也很好理解,也就一個XOR亦或運算。

明白了原理,我們還得講它轉化為程式的思想啊,我們用計算機的思維再來理下頭緒看看。

我們假設待測資料是1101 0110 11,生成項Poly是10011,假設有一個4 bits的暫存器,通過反覆的移位和進行CRC的除法,最終該暫存器中的值就是我們所要求的餘數。

                    3     2   1     0     Bits

                +---+---+---+---+

 Pop  <--  |     |      |     |     | <----- Augmented message(已加0擴張的原始資料)

                +---+---+---+---+

             1    0    0   1    1      =    The Poly 生成項

 

依據這個模型,我們得到了一個最簡單的演算法:


 1、把register中的值置0. 
   2、把原始的資料後新增w個0. 
   While (還有剩餘沒有處理的資料) 
      Begin 
      把register中的值左移一位,讀入一個新的資料並置於register最低位的位置。 
      If (如果上一步的左移操作中的移出的一位是1) 
         register = register XOR Poly. 
      End 

這裡的Poly是10011,字長是5,位寬w是4,則生成最後的CRC的位寬也是4,所以,我們用一個4位的暫存器就可以了。另外,目標位串最高位如果是0的話,則是簡單的移出去。真正參與運算的是1,而1 xor 1 = 0。所以我們可以將Poly最前面的1忽略,以及將Register移出的最高位忽略,因為這裡的運算總是0。

唔~~,好像都解釋一遍了吧,自己在腦海裡面模擬下這個過程。還不懂的就回頭再仔細看看,一些暫存器之類術語不懂的,我也不想解釋了,百度一下吧。

下面看下完整的程式:

#include "StdAfx.h"
#include "stdio.h"

int main()
{
	char POLY=0x13;          //生成項Poly,0x13=10011b,這樣CRC是4bit 
	unsigned short data = 0x035B;  //待測資料是0x35B = 1101011011b,12bit,注意,資料不是16bit 
	unsigned short regi = 0x0000;  //用0來填充暫存器的值,防止原先的垃圾值影響結果
	
	//按CRC計算的定義,待測資料後加入4位 0,以容納4bit的CRC; 
	//這樣共有16bit待測資料,從第5bit開始做除法,就要做16-5+1=12次XOR 
	data <<= 4; 
	
	// 按二進位制一位一位地開始計算
	for ( int cur_bit = 15; cur_bit >= 0; -- cur_bit )   //處理16次,前4次實際上只是載入資料 
	{ 
		//判斷最高位是否為1
		if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )     regi = regi ^ POLY; 
		
		regi <<= 1;   // 暫存器左移一位
		// 讀取目標位串下一位
		unsigned short tmp = ( data >> cur_bit ) & 0x0001;  //載入待測資料1bit到tmp中,tmp只有1bit 
		regi |= tmp;    //這1bit載入到暫存器中 
	} 
	if ( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )   regi = regi ^ POLY;    //做最後一次XOR 
	//這時, regi中的值就是CRC
	printf("0x%08X\n",regi);
	return 0;  
}

執行後列印出結果0x0000000E,也就是二進位制的1110b,結果正確。

唔~~,原理說了,演算法描述了,程式也給出來了。好像都完成了。其實不然,上面這個演算法效率比較低,每次只能處理1bit,這速度得計算到什麼時候啊,雖然計算機速度快,但也經不起耗啊,浪費資源,可恥。

那如何改進呢?如果我們能一次性運算一位元組是不是就會快點了呢?顯然的,但1 xor 1 =0,這很顯然的,一位元組怎麼xor呢,一位元組進行異或可以有256種結果,該怎麼做好呢?我們可以先建立一張表,把這些結果都計算出來,然後放到這張表裡面,為了方便索引,我們用暫存器移出的那一位元組位串來進行索引。聰明的你會說,不對啊,結果還跟Poly生成項有關啊,畢竟xor是個二元運算。呵呵,Poly是一定的,我們只要根據我們程式使用的Poly來生成一個表就行了,或者事先在程式儲存下來。在速度改進的同時,空間佔用會大一點,不過這點空間相對來說還是可以忽略的。

顯然,不同的生成項檢錯能力也不同,找到一個好的生成項需要具備比較好的數學基礎。但不用擔心,很多先驅已經為我們做好這件事。

以下是一些標準的CRC演算法的生成多項式:

標準

生成多項式

16進製表示

CRC12

x^12 + x^11 + x^3 + x^2 + x + 1

80F

CRC16

x^16 + x^15 + x^2 + 1

8005

CRC16-CCITT

x^16 + x^12 + x^5 + 1

1021

CRC32

x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11+ x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1

04C11DB7


其實,這個索引表,網上很多的地方都不一樣,很雜亂,很多人都是根據自己的理解來生成一個CRC表,這樣也可以滿足基本的校驗需要。其實這個表有好幾種,這裡只介紹一種,正規查詢表,也就是被正式引用的一種。Winrar對生成的壓縮包裡面的每個檔案都有一個CRC32校驗值,那就是用正規查詢表來生成的。但還不止這樣,還有各種顛倒(reflect)啊之類的,其實正規查詢表本身就是經過反射(reflect)的,有興趣的再下載附件裡面的資料來看看。這裡只給出實現。

先看看CRC32的正規查詢表吧

unsigned LONG CRC32Table[256] = {                                 
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D 
};

這個表其實可以在程式裡生成,下面給出的完整程式碼就是在程式裡面生成的。

#include "StdAfx.h"
#include "stdio.h"

//顛倒字串的順序
unsigned long int Reflect(unsigned long int ulReflect, int nBitCount)  
{ 
	unsigned long int value=0;  
	// 交換bit0和bit7,bit1和bit6,類推  
	for(int i = 1; i < (nBitCount + 1); i++)  
	{  
		if(ulReflect & 1)  
			value |= 1 << (nBitCount - i);  
		ulReflect >>= 1;  
	}  
	return value;  
} 

unsigned long CRC32(void * DataBuf,unsigned long ulLen) 
{
	unsigned long int crc,temp;  
	unsigned long int ulPolynomial = 0x04c11db7;  //標準的CRC32生成項
	static unsigned long int crc32_table[256] = {0}; 
	static int IsDoTable = 0;	
	//檢查是否已生成正規查詢表
	if (0 == IsDoTable)	
	{
		for(int i = 0; i <= 0xFF; i++)   // 生成CRC32“正規查詢表” 
		{  
			temp=Reflect(i, 8);  
			crc32_table[i]= temp<< 24;  
			for (int j = 0; j < 8; j++) 
			{  
				unsigned long int t1,t2;  
				unsigned long int flag=crc32_table[i]&0x80000000;  
				t1=(crc32_table[i] << 1);  
				if(flag==0)  
					t2=0;  
				else  
					t2=ulPolynomial;  
				crc32_table[i] =t1^t2 ;  
			}  
			crc=crc32_table[i];  
			crc32_table[i] = Reflect(crc32_table[i], 32);  
		}
		IsDoTable = 1;
	}

	//計算CRC32值 
	unsigned   long   lRes; 
	unsigned   long   ii;  
	unsigned   long   m_CRC = 0xFFFFFFFF;   //暫存器中預置初始值 
	char   *p = (char *)DataBuf;  
	for(ii=0;  ii <ulLen;  ii++)  
	{  
		m_CRC = crc32_table[( m_CRC^(*(p+ii)) ) & 0xff] ^ (m_CRC >> 8);  //計算 
	}  
	lRes= ~m_CRC;     //取反
	return lRes;
}

int main()
{
	char  DataBuf[512]={0x31,0x32,0x33,0x34};   //待測資料,為Ascii字串"1234" 
	printf("0x%08X\n",CRC32(DataBuf,4));
	return 0;  
}

下面是結果:


與WINRAR的完全一樣。

 

學了兩天CRC了,這只是一些總結。包括基本的原理及最終優化實現,但更深一層的優化演算法沒有細說,如果有讀者想要繼續瞭解更深一層的優化,可以下載附件來看看,在此再次感謝附件的作者。

忘了CSDN的附件是單獨管理的,這裡給出個資源連結吧:http://download.csdn.net/detail/epluguo/5980501

關於CRC的更多資料,而已參閱wiki百科:http://zh.wikipedia.org/zh-cn/%E5%BE%AA%E7%8E%AF%E5%86%97%E4%BD%99%E6%A0%A1%E9%AA%8C



相關文章