Delphi下实现数据校验

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

声明
本论文题目:Delphi下实现数据校验,作者:叶叶,于2012年11月5日在网易博客上发表。

页面地址:/blog/static/1972410212012105398312/ 。

本论文全文及相关配套程序可以在上述页面中下载。

请尊重他人劳动成果,转载或引用时请注明出处。

目录
1 技术简介 (3)
2 Adler-32算法 (4)
2.1 简介 (4)
2.2 实现 (4)
2.3 优化 (6)
2.4 分析 (7)
3 CRC-32算法 (7)
3.1 简介 (7)
3.2 实现 (8)
3.3 优化 (10)
3.4 反转CRC (14)
3.5 分析 (16)
4 MD5算法 (16)
4.1 简介 (16)
4.2 实现 (16)
4.3 优化 (23)
4.4 分析 (23)
5 SHA算法族 (25)
5.1 简介 (25)
5.2 SHA-1算法实现 (26)
5.3 SHA-256算法实现 (32)
5.4 SHA-224算法实现 (39)
5.5 SHA-512算法实现 (39)
5.6 SHA-384算法实现 (47)
5.7 SHA-512/224算法实现 (48)
5.8 SHA-512/256算法实现 (49)
5.9 优化 (49)
5.10 分析 (51)
6 结束语 (52)
参考文献 (52)
附录A 项目说明 (53)
A.1 程序项目 (53)
A.2 算法实现文件 (54)
A.3 算法测试 (55)
Delphi下实现数据校验
作者:叶叶(网名:yeye55)
摘要:介绍了数据校验的基本知识。

对Adler-32算法、CRC-32算法、MD5算法、SHA-1算法、SHA-224算法、SHA-256算法、SHA-384算法、SHA-512算法、SHA-512/224算法和SHA-512/256算法进行了逐一的介绍,并对这些算法的优化和安全性进行了讨论。

同时给出一个在Delphi 7.0下开发,可以对文件进行数据校验的应用程序。

关键词:数据校验;Adler-32;CRC-32;反转CRC;MD5;SHA;Delphi
中图分类号:TP301.6
1 技术简介
数据校验(Data Check)最早被用在数据传输领域中。

由于种种原因,数字信号在传输过程中不可避免地会产生差错。

例如在传输过程中受到外界的干扰,或在通信系统内部由于各个组成部分的质量不够理想而使传送的信号发生畸变等。

当受到的干扰或信号畸变达到一定程度时,就会产生差错。

而数据校验就是要发现这些差错,从而确保传输数据的完整性。

目前最常见的方法是利用一个算法对传输数据进行计算,从而得到一个校验码(Check Code),将校验码与传输数据一同发送。

在接收端使用相同的算法对传输数据进行计算,然后通过比较校验码就可以知道数据是否出现差错。

如果出现差错,接收端可以要求发送端重新发送。

有些算法产生的校验码仅能够检测出是否出现差错,这样的校验码被称为检错码(Error-detecting Code)。

而另一些算法产生的校验码不仅能够检测出错误,还可以纠正部分错误,这样的校验码被称为纠错码(Error-correcting Code)。

另外,在一些算法中将产生的校验码称为消息摘要(Message Digest),这类算法通常可以对任意长度的数据生成一个长度相同的校验码,以提供差错检测。

随着计算机技术的发展,数据校验开始被应用到安全领域中。

首先,可以利用校验码替换一些明文数据。

例如在数据库中保存用户密码时,只保存用户密码对应的校验码。

核对时,对用户输入的密码使用相同算法生成校验码,再进行对比。

这样就不用保存用户密码的明文,可以有效防止用户密码明文的泄露。

其次,数据校验也可以保护敏感数据不会被人为篡改。

对于一份敏感文档,可以对其生成相应的校验码,并交由第三方保存。

查看文档时可以交由第三方验证,确保文档的完整性。

再次,数据校验也被大量的使用在数字签名中。

发布数据时,对一份数据信息产生校验码,然后使用私钥对校验码进行加密。

加密后的校验码就是数字签名。

将数据信息和数字签名一同发送给接收方。

接收方使用公钥对数字签名进行解密,并使用相同的数据校验算法对接收到的数据信息生成校验码。

比较两份校验码就可以知道签名是否正确。

数字签名具有两种功能:第一,它可以保证数据信息是由发送方签名发出的;第二,它可以保证数据信息的完整性,因为不同的数据信息所产生的数字签名都是不一样的。

除此之外,数据校验在数字证书、数字版权管理、身份认证、数据库安全、文档交换等领域都有非常广泛的应用。

正是由于数据校验被应用到安全领域中,针对数据校验算法的破解也开始出现。

有一种破解方法被称为反译攻击法(Pre-image Attack)。

反译攻击法就是根据已有的校验码反推出原始数据的一种破解算法。

例如前面提到的,使用校验码代替用户密码的保存。

如果反译攻
击成功,那么利用校验码就可以得知用户的密码明文。

这对于用户系统来说将是灾难性的。

不过,从目前的技术来看,反译攻击法的实现还是十分困难的。

另一种破解方法被称为碰撞攻击法(Collision Attack)。

碰撞攻击法就是对已有的校验码进行计算,寻找到一份可以产生相同校验码的数据。

这份数据也许和原始数据不一致,但是它们可以产生完全相同的校验码。

碰撞攻击法如果可以实现,那么将使得伪造数据成为可能。

第三种破解方法被称为前缀碰撞法(Chosen-prefix Collision)。

前缀碰撞法是对原始数据进行篡改,然后在篡改后数据的末尾追加一段精心计算的数据,使得最后修改的数据与原始数据有着相同的校验码。

前缀碰撞法如果可以实现,那么将使得伪造数字签名成为可能。

正是由于这些破解方法的出现,数据校验算法的安全性也越来越得到重视。

现在的数据校验算法越来越复杂,产生的校验码也越来越长,这都是为了提高算法的安全性。

本文将对Adler-32算法、CRC-32算法、MD5算法、SHA-1算法、SHA-224算法、SHA-256算法、SHA-384算法、SHA-512算法、SHA-512/224算法和SHA-512/256算法进行逐一的介绍,并对这些算法的优化和安全性进行讨论。

同时给出一个在Delphi 7.0下开发,可以对文件进行数据校验的应用程序。

注意一点,本文介绍的这些数据校验算法都属于检错码算法。

纠错码算法更为复杂,本文不进行介绍,其算法原理请读者自行查阅相关资料。

2 Adler-32算法
2.1 简介
Adler-32是一种校验和(Checksum)算法,由Mark Adler在1995年提出,并应用到了zlib压缩库[1]中。

在RFC1950[2]中也有对于Adler-32算法的介绍,其中还给出了一个C语言的实现例子。

目前,Adler-32算法主要应用在zlib压缩库和一些网络协议中。

2.2 实现
Adler-32算法可以对任意长度的数据生成一个32位的校验码。

实现时,Adler-32算法需要计算两个16位的数值A和B。

A为所有输入字节的和,B为每步计算时A的和。

在算法开始时,将A初始化为1,B初始化为0。

计算结束后,需要将A和B分别对65521(小于216的最大质数)取余。

最后将计算结果连接起来形成一个32位的校验码。

假设有一份长度为n个字节的数据D n = d0, d1, d2, ..., d n-1 (0 ≤d i≤ 255),那么实现Adler-32算法的计算公式如下。

A = (1 + d0 + d1 + d2 + ... + d n-1) mod 65521
B = [(1 + d0) + (1 + d0 + d1) + (1 + d0 + d1 + d2) + ... + (1 + d0 + d1 + d2 + ... + d n-1)] mod 65521 Adler32(D n) = B * 65536 + A
现在让我们来看一个例子:假设有一个长度为10个字节的字符串“yeye55blog”,利用Adler-32算法对其计算校验码。

那么每一步计算的A和B的值如表2.1所示。

字符ASCII
码值
A B
y 121 1
+121=1220+122=122
e 101 122
+101=223122+223=345 y 121 223
+121=344345+344=689
e 101 344
+101=445689+445=1134
5 53 445
+53=4981134+498=1632
5 53 498
+53=5511632+551=2183
b 98 551
+98=6492183+649=2832 l 108 649
+108=7572832+757=3589 o 111 757
+111=8683589+868=4457
g 103 868
+103=9714457+971=5428
表2.1 每一步计算的A和B的值
最后经过计算,A的值为971,用十六进制表示就是$03CB;B的值为5428,用十六进制表示就是$1534。

由于它们都小于65521,可以直接对它们进行连接,得到$153403CB。

那么,字符串“yeye55blog”用Adler-32算法计算得到的32位校验码就是$153403CB。

可以看出Adler-32算法的实现非常简单,我们可以很方便的写出实现代码,如代码2.1所示。

01 function Adler32(const Buf; Len : Integer) : Cardinal;
02 const
03 BASE = 65521;
04 type
05 TByteAry = array [0..0] of Byte;
06 var
07 p : ^TByteAry;
08 SumA, SumB : Cardinal;
09 i : Integer;
10 begin
11 p := @Buf;
12 SumA := 1;
13 SumB := 0;
14
15 for i := 0 to Len - 1 do
16 begin
17 SumA := (SumA + p[i]) mod BASE;
18 SumB := (SumB + SumA) mod BASE;
19 end;
21 Result := (SumB shl 16) or SumA;
22 end;
代码2.1 Adler-32算法的直观实现
2.3 优化
代码2.1只是对Adler-32算法的直观实现,运行效率并不高。

观察代码可以发现,每步的求和运算都需要进行取模运算,这必定会严重影响算法的性能。

我们使用32位无符号整形的Cardinal类型来保存A和B,而每次求和只相加一个字节。

这意味着我们可以把取模运算延后进行。

假设A和B为可能出现的最大值65520,而现在输入的字节数据都是255。

在计算时只进行求和运算,不进行取模运算。

那么经过计算,输入5552个字节后B将达到上限,即4294690200($FFFBC598)。

如果此时再输入一个字节,那么B就会等于4296171735($1001260D7),从而出现溢出。

这个计算结果表明,我们可以先对5552个字节的数据进行求和,然后再进行取模运算。

在这期间B的值不会出现溢出。

而且延后进行取模运算可以有效的提高算法性能。

根据这个原理,我们可以对代码2.1进行优化。

优化后的代码如代码2.2所示。

01 function Adler32(const Buf; Len : Integer) : Cardinal;
02 const
03 BASE = 65521;
04 NMAX = 5552;
05 type
06 TByteAry = array [0..0] of Byte;
07 var
08 p : ^TByteAry;
09 SumA, SumB : Cardinal;
10 i, n : Integer;
11begin
12 p := @Buf;
13 SumA := 1;
14 SumB := 0;
15
16 while Len > 0 do
17 begin
18 if Len > NMAX then n := NMAX
19 else n := Len;
20
21 for i := 0 to n - 1 do
22 begin
23 Inc(SumA, p[i]); Inc(SumB, SumA);
24 end;
26 SumA := SumA mod BASE;
27 SumB := SumB mod BASE;
28
29 Inc(p, n); Dec(Len, n);
30 end;
31
32 Result := (SumB shl 16) or SumA;
33 end;
代码2.2 经过优化的Adler-32算法实现
代码2.2还可以进一步的优化。

注意一点,5552是可以被16整除的。

那么在每一个求和循环中可以进行16步的求和运算,进一步提高循环的效率。

另外,可以使用移位和减法来代替取模运算,用以提高算法速度。

进一步优化后的实现代码很长,这里就不再写出。

具体的代码可以参见本文的配套程序项目。

2.4 分析
从上述的介绍可以看出,Adler-32算法的原理是非常简单的。

正因为如此,Adler-32算法的安全性也是非常低的。

Adler-32算法产生的校验码非常容易被伪造。

所以Adler-32算法只适用于数据意外差错检测领域,而不能使用在会发生人为篡改的安全领域。

不过,有研究表明[3],在许多平台上Adler-32算法的速度比CRC-32算法更快。

2001年Jonathan Stone发现了Adler-32算法的一个缺点[4]。

那就是,对于较短的数据,Adler-32算法无法将数据分布到所有的位。

数据校验算法实际上是一种散列算法。

一个好的散列算法应该将数据“打散”,然后尽量平均分布到所有的输出位中。

Adler-32算法使用的是累加。

当输入数据较少时,累加的结果会集中在A和B的低位中,无法平均分布到所有的输出位。

从概率学的角度可以发现,较少的位只能表示较少的信息。

这就意味着,对于较短的数据,Adler-32算法很有可能会发生不同的数据出现相同校验码的情况。

正是由于这个原因,在RFC3309[5]中开始使用CRC-32c算法代替Adler-32算法。

目前,Adler-32算法主要应用在zlib压缩库和一些网络协议中。

虽然Adler-32算法的速度比CRC-32算法更快,但是由于自身的缺点,Adler-32算法正在逐步被CRC-32算法所代替。

3 CRC-32算法
3.1 简介
CRC的全称是循环冗余校验(Cyclic Redundancy Check,CRC),通常使用校验码的位长来命名CRC算法,例如生成8位校验码的CRC算法称为CRC-8;生成32位校验码的CRC 算法称为CRC-32。

CRC算法最早是由W. Wesley Peterson在1961年提出的[6]。

32位的CRC-32算法是在1975年随着以太网标准一同提出的。

由于CRC算法的特点,它可以很方便的使用
计算机硬件来实现。

所以在数据存储、数据总线和硬件数据交换等领域都大量使用CRC算法来进行数据校验。

许多用于数字通信的硬件芯片中都整合了实现CRC算法的数字电路。

不仅如此,CRC算法还被大量的应用在数据传输、网络协议、通信系统等领域。

可以说,CRC算法是一种使用最为广泛的数据校验算法。

3.2 实现
在本文中只讨论CRC算法的软件实现,其硬件实现请读者自行查阅相关资料。

实现CRC算法需要先定义一个生成多项式(Generator Polynomial),这里使用G(x)来表示生成多项式。

G(x)中的多项式系数必需是1或0,多项式的最高阶数必需等于校验码的位长。

一般情况下,G(x)多项式最高阶数和最低阶数的系数都设置为1。

另外,在表示G(x)的计算方法时,通常使用简写方式来表示。

现在来看一个例子,假设使用CRC-8算法来生成一个8位长的校验码,那么我们可以使用下面表3.1中定义的生成多项式。

生成多项式G(x) = 1*28 + 0*27 + 0*26 + 0*25 + 0*24 + 0*23 + 1*22 + 1*2 + 1
简写方式G(x) = x8 + x2 + x + 1
二进制值 100000111
十六进制值$107
表3.1 生成多项式的表示方法
假设校验码为R(x),校验码的位长为w,待校验的数据为M(x),生成多项式G(x)已经确定。

那么计算校验码R(x)的过程是:首先在数据M(x)的末尾添加w位的0,从而得到数据M’(x)。

然后将数据M’(x)除以G(x),计算得到的余数。

这个余数就是校验码R(x)。

将校验码R(x)追加到数据M(x)的尾部,形成一份带校验的数据Q(x)。

将Q(x)除以G(x),得到的余数肯定为0。

即,Q(x)可以被G(x)整除。

如果余数不为0,那么就说明Q(x)中的数据出现了差错。

这就是CRC算法的验证过程。

这里需要说明一点,这里所做的除法是模2除法。

模2除法的计算方法是:将除数和被除数的最高位的1对齐,然后进行异或运算。

不断重复这个过程,直到除数大于被除数。

剩下的被除数就是余数。

现在来看上面的例子,我们使用的生成多项式为G(x) = x8 + x2 + x + 1。

假设待校验数据的二进制值为1001101010101111,此时w为8,我们需要添加8个位的0。

整个计算过程如图3.1所示。

000000001111010101011001
xor
111000001
000000001111010010011000
xor
111000001
000000001111101010010000
xor
111000001
000000001110011010000000
xor
111000001
000000000000011000000000
xor
111000001
000000111000010000000000
xor
111000001
000001001000000000000000
xor
111000001
111001000000000000000000
图3.1 CRC算法的计算过程
最后得到的8个位的二进制值00100111就是校验码R(x)。

将它添加到数据M(x)的末尾可以得到二进制值100110101010111100100111。

这份带校验的数据Q(x)可以被G(x)整除,读者可以自行计算验证一下。

这个例子虽然是CRC-8算法的,但是它的原理对于其它位长的CRC算法也是通用的。

根据这个计算原理我们可以写出CRC-32算法的实现代码,如代码3.1所示。

01 function CRC32(const Buf; Len : Integer) : Cardinal;
02 const
03 POLY = $04C11DB7; //生成多项式
04 var
05 p : PByte;
06 i, n : Integer;
07 begin
08 Result := 0;
09
10 p := @Buf; n := 7;
11 for i := 1 to Len * 8 do
12 begin
13 if (Result and $80000000) <> 0 then
14 begin
15 Result := ((Result shl 1) or ((p^ shr n) and 1)) xor POLY;
16 end
17 else
18 begin
19 Result := (Result shl 1) or ((p^ shr n) and 1);
20 end;
21
22 if n <> 0 then Dec(n)
23 else
24 begin
25 Inc(p); n := 7;
26 end;
27 end;
28
29 for i := 1 to 32 do
30 begin
31 if (Result and $80000000) <> 0 then
32 begin
33 Result := (Result shl 1) xor POLY;
34 end
35 else
36 begin
37 Result := Result shl 1;
38 end;
39 end;
40 end;
代码3.1 CRC-32算法的直观实现
在代码3.1中使用的32位生成多项式是G(x) = x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1,使用十六进制表示为$104C11DB7。

在计算时通常将生成多项式的最高位省略。

因为计算时需要将最高位的1对齐进行异或运算,那么最高位的运算结果肯定是0。

代码3.1中使用的是按位计算,对于输入的数据一个位一个位的进行计算。

这样做的效率很低,这只是对于算法的直观实现。

代码3.1需要进一步的进行优化。

3.3 优化
在本节中将会使用到一些公式。

为了更好的描述算法的计算过程,在这些公式中将会使用到一些特殊的运算符号。

首先,在公式中使用“⊕”符号来表示异或运算。

异或运算就是不带进位的二进制加法运算,它的运算法则与加法相同。

其次,在公式中使用“mod2”符号来表示模2除法的取余运算。

模2除法取余运算的运算方法前面已经给出了一个例子,这里就不再重复。

它的运算法则与一般的模运算也是相同的。

注意一点,“mod2”符号是一个运算符号。

在代码3.1中使用了两个循环来实现算法,一个循环对数据进行计算,另一个循环对添加的32个位的0进行计算。

现在让我们来进行优化,将这两个循环合并起来。

在数据M(x)的末尾添加w位的0,这就相当于将M(x)乘以2w。

那么我们可以写出校验码R(x)的计算公式,如公式3.1所示。

)x (G mod )2)x (M ()x (R 2w ⋅=
(公式3.1)
现在让我们来看一个问题,假设已知一份数据M(x)的校验码为R(x)。

如果我们在这份数据的末尾追加一个字节,形成一份新的数据M”(x)。

那么新数据M”(x)的校验码R”(x)是多少?假设追加的字节为B(x),B(x)的位长为8位。

那么新数据M”(x)的计算公式如公式3.2所示。

)x (B )2)x (M ()x ("M 8⊕⋅=
(公式3.2)
这样我们可以将公式3.2代入公式3.1中用于计算数据M”(x)的校验码R”(x)。

代入并进行化简的计算过程如下。

)
x (G mod )2))2)x (B ()x (R (()x (G mod ))2)x (B ()2)x (R (())x (G mod )2)x (B (())x (G mod )2)x (R (())x (G mod )2)x (B (())x (G mod )2))x (G mod )2)x (M (((())
x (G mod )2)x (B (())x (G mod )2)x (M (()
x (G mod ))2)x (B ()2)x (M (()
x (G mod )2))x (B )2)x (M ((()x ("R 288w 2w 82w 282w 282w 2w 28w 2w 8w 2w 8⋅⋅⊕=⋅⊕⋅=⋅⊕⋅=⋅⊕⋅⋅=⋅⊕⋅=⋅⊕⋅=⋅⊕⋅=−++
最终,我们可以得到根据已知的校验码R(x)计算新数据的校验码R”(x)的计算公式,如公式3.3所示。

)x (G mod )2))2)x (B ()x (R (()x ("R 288w ⋅⋅⊕=−
(公式3.3)
分析公式3.3可以发现,校验码R(x)的位长就是w ,将B(x)乘以2w-8就是将B(x)左移到R(x)的最高字节的位置。

那么公式3.3可以表述为:追加字节B(x)与原校验码R(x)的最高字节进行异或运算,然后将结果左移8位,再进行模2除法的取余运算。

最终结果就是新校验码R”(x)的值。

根据这个分析结果我们可以写出优化后的实现代码,如代码3.2所示。

01function CRC32(const Buf; Len : Integer) : Cardinal; 02const
03 POLY = $04C11DB7; //生成多项式 04var 05 p : PByte; 06 i, n : Integer; 07begin
08 Result := 0; 09
10 p := @Buf; 11 for i := 1 to Len do 12 begin
13 Result := Result xor (p^ shl 24); Inc(p); 14
15 for n := 1 to 8 do 16 begin 17 if (Result and $80000000) <> 0 then 18 begin
19 Result := (Result shl 1) xor POLY; 20 end 21 else 22 begin
23 Result := Result shl 1; 24 end; 25 end; 26 end; 27
end;
代码3.2 合并两个循环后的实现代码
与代码3.1相比,代码3.2已将两个循环合并。

并且代码3.2一次处理一个字节,效率更高。

当然我们还可以再进一步的进行优化。

观察代码3.2我们可以发现,与生成多项式的异或运算都集中在最高字节内完成。

根据异或运算的结合律,即:)c b (a c b a ⊕⊕=⊕⊕。

我们可以先对最高字节中的数据进行计算,然后再合并计算结果。

经过这样修改的代码如代码3.3所示。

01 function CRC32(const Buf; Len : Integer) : Cardinal;02 const 03 POLY = $04C11DB7; //生成多项式 04 var 05 p : PByte; 06 i, n : Integer; 07 Temp : Cardinal; 08 begin 09 Result := 0; 10 11 p := @Buf; 12 for i := 1 to Len do 13 begin
14 Temp := (Result shr 24) xor p^; Inc(p); 15
16 Temp := Temp shl 24;
17 for n := 1 to 8 do
18 begin
19 if (Temp and $80000000) <> 0 then
20 begin
21 Temp := (Temp shl 1) xor POLY;
22 end
23 else
24 begin
25 Temp := Temp shl 1;
26 end;
27 end;
28
29 Result := (Result shl 8) xor Temp;
30 end;
31 end;
代码3.3 先对最高字节进行计算的实现代码
与代码3.2相比,代码3.3增加了一个变量Temp。

变量Temp保存了最高字节的数据,然后进行计算,最后再合并到返回结果中。

从表面上看,这样做使得代码变得更为复杂。

然而,仔细观察变量Temp可以发现,变量Temp最初只是一个字节的数据,这意味着它的取值范围在0至255之间。

那么我们可以将变量Temp的所有可能的值事先计算出来,形成一份数据表,然后在计算过程中就可以直接进行查表计算。

这就是CRC算法实现中的查表法,而对应的用于计算的数据表通常被称为余式表(Remainder Table)。

用查表法实现CRC算法,其代码可以分为两个部分。

一部分代码用于生成余式表。

这有两种方案。

一种方案是在计算开始前临时生成余式表。

这种方案的好处是可以根据需要使用不同的生成多项式,但是它的速度相对较慢。

另一种方案是生成余式表的常量定义代码,将生成的代码直接嵌入计算程序。

这种方案只能使用固定的生成多项式,如果要修改生成多项式就要修改程序代码。

但是这种方案的速度较快。

在本文中就是使用这第二种方案。

计算余式表的表项值的代码就相当于代码3.3中16行至27行的代码。

调用时对变量Temp从0至255进行遍历,逐一进行计算。

将计算结果输出就可以得到余式表。

在生成余式表后,另一部分的代码就是根据余式表计算校验码。

这部分的代码如代码3.4所示。

01 function CRC32(const Buf; Len : Integer) : Cardinal;
02 const
03 //余式表
04 Table : array [0..255] of Cardinal = (
05 $00000000, $04C11DB7, $09823B6E, $0D4326D9,
06
07 // 此处省略部分代码……
08
09 $BCB4666D, $B8757BDA, $B5365D03, $B1F740B4
10 );
11 var
12 p : PByte;
13 i : Integer;
14 begin
15 Result := 0;
16
17 p := @Buf;
18 for i := 1 to Len do
19 begin
20 Result := (Result shl 8) xor
21 Table[(Result shr 24) xor p^];
22 Inc(p);
23 end;
24 end;
代码3.4 用查表法实现CRC算法的部分代码
从代码3.4中可以看出,此时的实现代码已经变的非常简单。

代码3.4需要使用一份常量表来进行计算,这份常量表会占用1024字节的空间。

这是一种典型的用空间换时间的方案。

在本文的推导过程中,使用的是在数据后追加一个字节的情况。

当然,还可以有其它情况。

例如在数据后追加4个位的数据。

这样经过推导,最终的常量表只需要占用64个字节的空间。

但是,代码的运行速度将会变的更慢。

这需要在空间占用和运行时间中找到一个平衡点。

最后说明一点,代码3.4只是部分代码。

完整的代码以及余式表的生成代码请参见本文的配套程序项目,这里就不再写出。

3.4 反转CRC
上面介绍的CRC算法有一个重大的漏洞。

对于一份全部由0组成的数据,不管这份数据的长度是多少,它的CRC校验码都是0。

这就意味着,如果一份数据的头部全部都是由0组成的,而在传输的过程中数据头部丢失了部分字节,那么CRC算法将无法检测出这样的错误。

为了解决这个问题,出现了一种CRC算法的变体,被称为反转CRC(Reversed CRC)。

与之相对应的,前面介绍的CRC算法被称为正常CRC(Normal CRC)。

反转CRC就是将数据反转过来。

数据的最高位反转到最低位,数据的最低位反转到最高位;数据的次高位反转到次低位,数据的次低位反转到次高位;并以此进行类推。

对于输入的数据,正常CRC 总是从输入字节的最高位开始计算,直到最低位。

而反转CRC从输入字节的最低位开始计算,直到最高位。

与此同时,用于计算的生成多项式也需要进行反转。

在前面的代码中所使用的32位生成多项式对应于正常CRC和反转CRC的二进制值如表3.2所示。

生成多项式x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1
正常CRC 100000100110000010001110110110111
反转CRC 111011011011100010000011001000001
表3.2 正常CRC和反转CRC的二进制值
正常CRC生成多项式的值去掉最高位的1,用十六进制表示为$04C11DB7。

反转CRC 生成多项式的值去掉最低位的1,用十六进制表示为$EDB88320。

在实现反转CRC算法的运算中,也要进行相应的“反转”。

不仅如此,在反转CRC算法中使用一个全1的值来初始化校验码。

而且在完成全部计算之后,还要对校验码的值进行取反运算。

以代码3.3为基础,将其修改为反转CRC算法的实现代码,如代码3.5所示。

01 function CRC32(const Buf; Len : Integer) : Cardinal;
02 const
03 POLY = $EDB88320; //生成多项式
04 var
05 p : PByte;
06 i, n : Integer;
07 Temp : Cardinal;
08 begin
09 Result := $FFFFFFFF;
10
11 p := @Buf;
12 for i := 1 to Len do
13 begin
14 Temp := (Result xor p^) and $FF; Inc(p);
15
16 for n := 1 to 8 do
17 begin
18 if (Temp and 1) <> 0 then
19 begin
20 Temp := (Temp shr 1) xor POLY;
21 end
22 else
23 begin
24 Temp := Temp shr 1;
25 end;
26 end;
27
28 Result := (Result shr 8) xor Temp;
29 end;
30
31 Result := not Result;
32 end;
代码3.5 反转CRC算法的实现代码
比较代码3.3与代码3.5,除了初始值和最后的取反运算的不同,其中所有的左移运算都“反转”成了右移运算。

另外,现在的追加字节是与原校验码的最低字节进行异或运算。

对于变量Temp的位测试,也改成了最低位的测试。

其余的部分,代码3.3与代码3.5完全
一样。

反转CRC也是可以使用查表法来实现的。

以代码3.5为基础,进行适当的修改就可以完成。

这个过程与代码3.3修改成为代码3.4的过程是完全一样的。

这里就不再写出代码,详细的代码请参见本文的配套程序项目。

最后还有一点需要说明,反转CRC的验证过程不能像正常CRC那样进行。

将校验码追加到数据末尾,用反转CRC算法无法实现“整除”。

反转CRC算法的验证过程只有通过比对校验码来实现。

3.5 分析
虽然说CRC算法是一种使用最为广泛的数据校验算法。

但是CRC算法的应用大多集中在数据意外差错检测领域。

在安全领域很少使用CRC算法,因为CRC算法是比较容易被破解的。

CRC算法检测数据差错的能力与校验码的位长和生成多项式的结构密切相关。

从理论上来说,校验码位长越长算法的检错能力就越好。

当然,生成多项式的结构也很重要。

一个好的生成多项式的结构,即使使用较短的校验码位长,也可以获得较好的检错能力。

已经有众多的学者对生成多项式的结构进行了大量的研究,并给出了大量的研究报告、技术论文以及技术标准,这里就不再一一列举了。

正是由于CRC算法可以使用不同的校验码位长和生成多项式结构,使得CRC算法出现了众多的版本。

而且CRC算法还有正常CRC和反转CRC 的区别。

所以在编写程序时需要多加注意。

如果需要编写兼容性程序,一定要仔细查阅对方所使用的CRC算法的技术标准。

虽然目前已经出现了64位的CRC-64算法。

但是由于CRC算法自身的特点,更大位长的CRC算法已经被其它算法所代替。

例如128位的CRC-128算法由MD5算法所代替,160位的CRC-160算法由SHA-1算法所代替。

4 MD5算法
4.1 简介
MD的全称是消息摘要(Message Digest,MD,又译“报文摘要”)。

MD5算法由Ronald L. Rivest在1992年提出[7]。

MD5指的是该算法的第五个版本,之前的算法有MD2算法、MD3算法和MD4算法。

在MD5算法中将生成的校验码称为消息摘要。

MD5算法可以对任意长度的数据生成128位长度的消息摘要。

MD5算法被广泛的应用于安全领域和文件校验领域。

在许多软件中也使用MD5算法来进行数据校验。

不过,由于MD5算法的安全性问题,一些安全领域中已经开始放弃使用MD5算法。

4.2 实现
首先需要说明一些术语。

在MD5算法中一个“字”表示一个32位无符号整形数据。

而一个“字节”表示一个8位无符号整形数据。

另外,在MD5算法中处理数据时使用的是小端(Little-endian)字节顺序,即,先保存低位字节数据,再保存高位字节数据。

在实现MD5算法的时候,经常需要将输入的4个字节的数据转换为1个字的数据。

这时就需要对。

相关文档
最新文档