傳輸數據校驗算法研究


 今天簡單介紹一些傳輸數據校驗的方法,就昨天整理的資料和就我的理解寫的Demo做個總結!希望大家多多指教!

定義

通俗的說,就是為保證數據的完整性,用一種指定的算法對原始數據計算出的一個校驗值。接收方用同樣的算法計算一次校驗值,如果和隨數據提供的校驗值一樣,說明數據是完整的。

實際應用

防止自己的程序被篡改。
有些可執行程序,當被改了資源時再運行會有文件已損壞的提示,這就是使用了數據校驗。本例是用md5做為數據校驗的算法。當然你可以使用個性化的
比如des作為數字簽名,那樣安全性更高。
 

校驗方法

數據校驗無非就是三個步驟:一、添加校驗碼;二、校驗數據;三、還原數據。   如下圖:(具體方法后面一一介紹)

 先看看這次咱們學習哪些,我在里面定義的一個枚舉

        public enum VerifyType
        {
            /// <summary>
            /// 無校驗
            /// </summary>
            None,
            /// <summary>
            /// 奇校驗
            /// </summary>
            Odd,
            /// <summary>
            /// 偶校驗
            /// </summary>
            Even,
            /// <summary>
            /// 1校驗
            /// </summary>
            Mark,
            /// <summary>
            /// 0校驗
            /// </summary>
            Space,
            /// <summary>
            /// 循環冗余碼CRC檢驗
            /// </summary>
            CRC,
            /// <summary>
            /// 異或校驗
            /// </summary>
            BCC,
            /// <summary>
            /// 和校驗
            /// </summary>
            Sum,
            /// <summary>
            /// MD5
            /// </summary>
            MD5
        }

然后就是對應這三個方法的測試調用了:

 static void Main(string[] args)
        {
            //校驗方式選擇
            Verify.VerifyType T = Verify.VerifyType.None;

            Console.WriteLine("驗證方式{0} :\n==================================================================\n\n", T);

            byte[] sendDate = { 10, 252, 253, 254, 255, 50, 51, 66, 85, 11 };
            Console.WriteLine("待發送數據:");
            for (int i = 0; i < sendDate.Length; i++)
            {
                Console.Write(sendDate[i] + " ");
            }
            Console.WriteLine();


            //添加校驗碼
            sendDate = Verify.AddCode(sendDate, T);
            Console.WriteLine("增加校驗碼的發送數據:");
            for (int i = 0; i < sendDate.Length; i++)
            {
                Console.Write(sendDate[i] + " ");
            }
            Console.WriteLine("\n…………………………………………………………………………………………………………\n\n");

            Console.WriteLine("接收數據校驗:------------");
            if (Verify.CheckCode(sendDate, T))
            {
                Console.WriteLine("校驗成功");
                sendDate = Verify.RestoringData(sendDate, T);
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("校驗失敗!!!!");
            }
            for (int i = 0; i < sendDate.Length; i++)
            {
                Console.Write(sendDate[i] + " ");
            }

            Console.ReadKey();
        }

 

串口通訊過程中有五種校驗方式,分別是無校驗(None),奇校驗(Odd),偶校驗(Even),1校驗(Mark),0校驗(Space)。

無校驗(None)就是直接發送數據沒有校驗位,所以沒什么好介紹的,直接看看運行結果吧!

 

奇偶校驗Parity Check (下面介紹來自百度百科)

偶校驗(Parity Check)是一種校驗代碼傳輸正確性的方法。根據被傳輸的一組 二進制代碼的數位中“1”的個數是奇數或偶數來進行校驗。采用奇數的稱為 奇校驗,反之,稱為 偶校驗。采用何種校驗是事先規定好的。通常專門設置一個 奇偶校驗位,用它使這組代碼中“1”的個數為奇數或偶數。若用 奇校驗,則當接收端收到這組代碼時,校驗“1”的個數是否為奇數,從而確定傳輸代碼的正確性。
奇偶校驗能夠檢測出信息傳輸過程中的部分 誤碼(奇數位 誤碼能檢出,偶數位誤碼不能檢出),同時,它不能糾錯。在發現錯誤后,只能要求重發。但由於其實現簡單,仍得到了廣泛使用。
 
為了能檢測和糾正內存軟錯誤,首先出現的是內存“奇偶校驗”。內存中最小的單位是比特,也稱為“位”,位只有兩種狀態分別以1和0來標示,每8個連續的比特叫做一個字節(byte)。不帶奇偶校驗的內存每個字節只有8位,如果其某一位存儲了錯誤的值,就會導致其存儲的相應數據發生變化,進而導致 應用程序發生錯誤。而奇偶校驗就是在每一字節(8位)之外又增加了一位作為錯誤檢測位。在某字節中存儲數據之后,在其8個位上存儲的數據是固定的,因為位只能有兩種狀態1或0,假設存儲的數據用位標示為1、1、1、0、0、1、0、1,那么把每個位相加(1+1+1+0+0+1+0+1=5),結果是奇數。對於偶校驗,校驗位就定義為1,反之則為0;對於奇校驗,則相反。當 CPU讀取存儲的數據時,它會再次把前8位中存儲的數據相加,計算結果是否與校驗位相一致。從而一定程度上能檢測出內存錯誤,奇偶校驗只能檢測出錯誤而無法對其進行修正,同時雖然雙位同時發生錯誤的概率相當低,但奇偶校驗卻無法檢測出雙位錯誤。

簡單例子:

奇校驗(Odd),這里是應用在軟件傳輸上的校驗而不是硬件編程而且是例子所以寫的比較簡單易懂

算法:

 1         /// <summary>
 2         /// 奇校驗算法獲得校驗碼
 3         /// </summary>
 4         /// <param name="date"></param>
 5         /// <returns></returns>
 6         private static byte[] OddCode(byte[] data)
 7         {
 8             byte[] bVerify = new byte[data.Length / 8 + 2];//校驗位接收數組
 9             bVerify[bVerify.Length - 1] = (byte)bVerify.Length;//校驗位長度
10             //計算校驗位數據
11             for (int i = 0; i < data.Length; i++)//每個位去處理
12             {
13                 if (GetOddEvenVerify(data[i]))
14                 {//奇數 //0;
15                     clr_bit(ref bVerify[i / 8], i);
16                 }
17                 else
18                 {//偶數//1;
19                     set_bit(ref bVerify[i / 8], i);
20                 }
21             }
22             return bVerify;
23         }
24         /// <summary>
25         /// 判斷當前位1的個數是奇數個還是偶數個
26         /// </summary>
27         /// <param name="bData"></param>
28         /// <returns>True 為奇數個 False 為偶數個</returns>
29         private static bool GetOddEvenVerify(byte bData)
30         {
31             byte bcCount = 0;    /* 字節內1的個數 */
32 
33             for (int i = 0; i < 8; i++)
34             {
35                 if ((bData & (byte)0x01) == 1)
36                 {
37                     bcCount++;
38                 }
39                 bData >>= 1;
40             }
41 
42             return ((bcCount & (byte)0x01) == 1);
43         }
44         /// <summary>
45         /// 置位x的y位 
46         /// </summary>
47         /// <param name="x"></param>
48         /// <param name="y"></param>
49         private static void set_bit(ref byte x, int y)
50         {
51             x = (byte)(x | (0x01 << (y)));
52 
53         }
54         /// <summary>
55         /// 清零x的y位 
56         /// </summary>
57         /// <param name="x"></param>
58         /// <param name="y"></param>
59         private static void clr_bit(ref byte x, int y)
60         {
61             x = (byte)(x & ~(0x01 << (y)));
62         }

無論是奇校驗還是偶校驗都是需要統計校驗位數據中1的個數GetOddEvenVerify,還有就是對校驗碼相應位進行標記為1還是0的方法 set_bit 和 clr_bit 這三個公用的方法,然后就是對原始數據進行計算得到校驗碼並附到原始數據后面(當然你也可以放到前面)。

在這我就用了1byte 作為標記校驗位的長度所以能標記的范圍是很有限的只有255位

添加校驗 :

             case VerifyType.Odd:
                    if ((data.Length / 8 + 1) > 255) return newDate;//校驗位長度大於255就超出這次比較算法處理范圍---收斂

                    byte[] bVerifyOdd = OddCode(data);
                    newDate = new byte[data.Length + bVerifyOdd.Length];
                    data.CopyTo(newDate, 0);
                    bVerifyOdd.CopyTo(newDate, data.Length);
                    break;

 校驗數據 : 

            case VerifyType.Odd:
                    int lenOdd = data[data.Length - 1];
                    byte[] sourceBVerifyOdd = new byte[lenOdd];
                    sourceBVerifyOdd = data.Skip(data.Length - lenOdd).Take(lenOdd).ToArray();
                    byte[] nowBVerifyOdd = OddCode(RestoringData(data, t));
                    return sourceBVerifyOdd.SequenceEqual(nowBVerifyOdd);

還原數據 :(奇校驗 偶校驗數據還原都是一樣的算法)

                case VerifyType.Odd:
                case VerifyType.Even:
                    return data.Take(data.Length - (data[data.Length - 1])).ToArray();    

運行結果:

偶校驗(Even)

算法:(與奇校驗算法唯一不同的就是補碼位的0和1正好相反

        /// <summary>
        /// 偶校驗算法獲得校驗碼
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        private static byte[] EvenCode(byte[] data)
        {
            byte[] bVerify = new byte[data.Length / 8 + 2];//校驗位接收數組
            bVerify[bVerify.Length - 1] = (byte)bVerify.Length;//校驗位長度
            //計算校驗位數據
            for (int i = 0; i < data.Length; i++)//每個位去處理
            {
                if (GetOddEvenVerify(data[i]))
                {//奇數 //1;
                    set_bit(ref bVerify[i / 8], i);
                }
                else
                {//偶數//0;
                    clr_bit(ref bVerify[i / 8], i);
                }
            }
            return bVerify;
        }

添加校驗碼:

                case VerifyType.Even:
                    if ((data.Length / 8 + 1) > 255) return newDate;//校驗位長度大於255就超出這次比較算法處理范圍---收斂

                    byte[] bVerifyEven = EvenCode(data);
                    newDate = new byte[data.Length + bVerifyEven.Length];
                    data.CopyTo(newDate, 0);
                    bVerifyEven.CopyTo(newDate, data.Length);
                    break;

檢驗校驗碼:

              case VerifyType.Even:
                    int lenEven = data[data.Length - 1];
                    byte[] sourceBVerifyEven = new byte[lenEven];
                    sourceBVerifyEven = data.Skip(data.Length - lenEven).Take(lenEven).ToArray();
                    byte[] nowBVerifyEven = EvenCode(RestoringData(data, t));
                    return sourceBVerifyEven.SequenceEqual(nowBVerifyEven);

運行結果:

 

1校驗(Mark),0校驗(Space)

校驗方式設置為1校驗(Mark),校驗位固定為1;如果校驗方式設置為0校驗(Space),校驗位固定為0;

添加校驗碼:

                case VerifyType.Mark:
                    newDate = new byte[data.Length + 1];
                    data.CopyTo(newDate, 0);
                    newDate[data.Length] = 1;
                    break;
                case VerifyType.Space:
                    newDate = new byte[data.Length + 1];
                    data.CopyTo(newDate, 0);
                    newDate[data.Length] = 0;
                    break;

檢驗校驗碼:

                case VerifyType.Mark:
                    return data[data.Length - 1] == 1;
                case VerifyType.Space:
                    return data[data.Length - 1] == 0;

數據還原:

                case VerifyType.Mark:
                case VerifyType.Space:
                case VerifyType.BCC:
                    return data.Take(data.Length - 1).ToArray();

運行結果:

 

 

bcc異或校驗法(block check character)

實現方法:很多基於串口的通訊都用這種既簡單又相當准確的方法。它就是把所有數據都和一個指定的初始值(通常是0)異或一次,最后的結果就是校驗值,通常把它附在通訊數據的最后一起發送出去。接收方收到數據后自己也計算一次異或和校驗值,如果和收到的校驗值一致就說明收到的數據是完整的。
適用范圍:適用於大多數要求不高的 數據通訊
應用例子:ic卡接口通訊、很多單片機系統的 串口通訊都使用。
算法:
        /// <summary>
        /// 異或校驗 校驗碼
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static byte ParityCode(byte[] data)
        {
            byte CheckCode = 1;
            //異或校驗
            for (int i = 0; i < data.Length; i++)
            {
                CheckCode ^= data[i];
            }
            return CheckCode;
        }

添加校驗碼:

                case VerifyType.BCC:
                    newDate = new byte[data.Length + 1];
                    data.CopyTo(newDate, 0);
                    newDate[data.Length] = ParityCode(data);
                    break;

數據校驗:

                case VerifyType.BCC:
                    return (ParityCode(RestoringData(data, t)) == data[data.Length - 1]);

數據還原與之前 1校驗和0校驗一樣,截去最后一位

運行結果:

和校驗

這里拿一個int值作為保存結果所以是32位的4byte的校驗碼

算法:

        /// <summary>
        /// 累加和
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static byte[] SumCode(byte[] data)
        {
            int sum = 0;
            foreach (var item in data)
            {
                sum += item;
            }
            byte[] code = intToBytes(sum);
            return code;
        }

添加校驗碼:

                case VerifyType.Sum:
                    newDate = new byte[data.Length + 4];
                    data.CopyTo(newDate, 0);
                    SumCode(data).CopyTo(newDate, data.Length);
                    break;

數據校驗:

                case VerifyType.Sum:
                    //方法一:
                    //int sourceSum = byteToInt(data.Skip(data.Length - 4).ToArray());
                    //int newSum = byteToInt(SumCode(RestoringData(data, t)));
                    //return sourceSum.Equals(newSum);
                //方法二:
                return data.Skip(data.Length - 4).ToArray().SequenceEqual(SumCode(RestoringData(data, t)));        

還原數據:

                case VerifyType.Sum:
                    return data.Take(data.Length - 4).ToArray();

運行結果:

 

MD5校驗和數字簽名

實現方法:主要有md5和des算法。
適用范圍:數據比較大或要求比較高的場合。如md5用於大量數據、 文件校驗,des用於保密數據的校驗( 數字簽名)等等。
應用例子: 文件校驗、銀行系統的交易數據
算法:( 這里用的是32位MD5算法
        /// <summary>
        /// 計算data字節數組的哈希值
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static byte[] MD5Code(byte[] data)
        {
            System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
            return md5.ComputeHash(data);//計算data字節數組的哈希值
        }

添加校驗碼(數據簽名):

                case VerifyType.MD5:
                    byte[] md5data = MD5Code(data);//計算data字節數組的哈希值
                    newDate = new byte[data.Length + md5data.Length];
                    data.CopyTo(newDate, 0);
                    md5data.CopyTo(newDate, data.Length);
                    break;

數據校驗:

                case VerifyType.MD5:
                    byte[] sourceMD5 = data.Skip(data.Length - 16).ToArray();
                    byte[] newMD5 = MD5Code(RestoringData(data, t));//計算data字節數組的哈希值
                    return sourceMD5.SequenceEqual(newMD5);

數據還原:

                case VerifyType.MD5:
                    return data.Take(data.Length - 16).ToArray();

運行結果:

 

crc循環冗余校驗(Cyclic Redundancy Check)

實現方法:這是利用除法及余數的原理來進行錯誤檢測的.將接收到的碼組進行除法運算,如果除盡,則說明傳輸無誤;如果未除盡,則表明傳輸出現差錯。crc校驗具還有自動糾錯能力。
crc檢驗主要有 計算法查表法兩種方法,網上很多實現代碼。今天下班了,我這里就不寫了,只要知道算法的思路就可以很容易寫出來的。
適用范圍:CRC-12碼通常用來傳送6-bit字符串;CRC-16及CRC-CCITT碼則用是來傳送8-bit字符。CRC-32:硬盤數據,網絡傳輸等。
應用例子:rar,以太網卡芯片、MPEG解碼芯片中
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM