传输数据校验算法研究

 今天简单介绍一些传输数据校验的方法,就昨天整理的资料和就我的理解写的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} :
==================================================================

", 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("
…………………………………………………………………………………………………………

");

            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解码芯片中
 
原文地址:https://www.cnblogs.com/cyr2012/p/4287872.html