DES的英文全称是Data Encryption Standard,意思是数据加密标准。而我们本篇文章讨论的是DES的加密算法。希望大家能够将这两个名词区别开来,很多时候我们说的DES都是在指DES算法,而不是DES数据加密标准。DES算法是一种典型的分组密码,即将固定长度的明文通过一系列复杂的操作变成同样长度密文的算法。也就是运行一次DES算法只能对64位长度的数据进行加密操作,这样做的缺点就是如果处理大量数据就会花费相当长的时间,所以DES算法也就不适合对大数据进行处理。DES在现代密码学中属于对称加密算法,即该算法能够使用相同的密钥进行加密和解密。
DES算法在设计中使用了分组密码设计的两个原则:混淆和扩散,混淆是使密文的统计特性与密钥的取值之间的关系尽可能复杂化,以使密钥和明文以及密文之间的依赖性对密码分析者来说是无法利用的。扩散的作用就是将每一位明文的影响尽可能迅速地作用到较多的输出密文位中,以便在大量的密文中消除明文的统计结构,并且使每一位密钥的影响尽可能迅速地扩展到较多的密文位中,以防对密钥进行逐段破译。主要目的就是增加数据被破解的难度,造成雪崩效应,即在输入时即使发生了微小的变化都会对结果产生不可分辨性的变化。
DES算法使用的是Feistel框架,Feistel框架的密码结构是用于分组密码中的一种对称结构,Feistel框架使用的是对同一处理流程进行多次循环的方式。DES算法就是采用的16轮循环的加密,当然循环次数越多加密的效果越好,同时时间成本也会上去,设计者需要考虑这两方面的因素进行密码设计。因为这个框架是用于处理分组密码的,我们就要了解一下什么是分组密码,分组密码是一种加解密方案,将输入的明文分组当作一个整体处理,并输出一个等长的密文分组,典型的分组大小为64位和128位,随着对加密算法安全性能的要求不断提高,分组大小也就会越来越大。DES算法所采用的分组大小为64位分组,所以输入的数据和密钥都是按照64位进行处理的,输出的密文也是64位一组。
(从这里大家也可以看出来,在进行最后一轮循环时R[15]被赋值给L[16],L[15]经过一系列运算后结果赋值给了R[16],这里进行了一次交换,而在逆初始值置换之前又进行了一次交换,相当于没有变化。这里其实是为了保持16轮循环处理的过程一致,方便学习。如果大家觉得需要优化下,完全可以将R[15]直接赋值给R[16],L[15]经过一系列运算后结果赋值给L[16],这是完全没有问题的。)
置换处理:在密码学中置换是指在保持数据不变的情况下,打乱数据的位置顺序的操作称作置换,在DES算法中每个置换处理都会按照 相应的置换表进行操作,置换处理在DES算法中得到了充分的运用,通过置换处理可以打乱输入数据的顺序,使输入的数据变得面目全非,当然也会造成雪崩效应,因为只要有一个数据位发生变化,就会影响到很多地方。
初始置换表:
这个初始置换表怎么使用呢?如何通过该表进行DES初始置换呢?
首先我们就要把明文的64位数据按照从左到右,从上到下进行排列,形成一个8*8的矩阵。我们以初始置换表的第0行第0列为例进行讲解,在初始置换表中该位置的数值是58,我们就要将数据中的第58个Bit位移动到第一个Bit位上,这就完成了一次置换。因为这个表的大小有64位,所以就要进行64次置换才会得出结果。
图示:
代码演示:
从表面上看,这里就是将上面置换后的结果进行分组,每组32位数据。实际上这里是有一个坑的,因为从字面意思上理解他是要将8*8矩阵的数据分成左右两组,也就是左边4列为L组,右边数据为R组。但是实际上网上的DES加密工具所进行的分组不是这样子的,如果按照左右分组,最终得到的结果也就和网上加密工具的结果不一致,同样也就无法解密数据。所以我们这里就使用前后分组来进行处理,将前32为数据分给L组,后32位数据分给R组,保持最后的结果一致。
分组图示:
理论分组代码演示:
实际分组代码演示:
在DES算法中F函数蕴含了DES算法的主要操作,也是该算法中最重要的地方。从DES算法的Feistel框架图中,我们可以清楚的看到F函数有两个输入,一个是右分组的32位数据,另一个是子密钥的48位数据,这两个长度不同的数据是怎样进行处理的呢?这里我们就先来看看它的处理流程图吧:
F函数处理流程:
这里看起来比较复杂我们接下来就把赋值的问题简单化,对他进行逐个击破。我们就从上往下进行分析:
在扩展置换上面,我们发现了一个F函数的输入数据——32位的右分组R[i-1]。在它经过扩展置换后数据长度就变成48位了,到底发生了什么样的变化呢?我们之前说过置换不会改变数据的内容,只会改变数据的顺序,至于是什么样的顺序就要通过置换表了解了。现在就上扩展置换表:
从扩展置换表中我们可以看出如果把扩展的结果当作6*8的数据矩阵,其中中间的四列是R组的32位原始数据,两边的两列就是扩展的重复数据,通过这种扩展就可以完美的将32位数据变成48位数据。该置换的主要目的是在加密数据的过程中制造一些雪崩效应,使用数据块中的1位将在下一步操作中影响更多位,从而产生扩散效果。
代码演示:
S盒置换是F函数中最重要的处理,也是最麻烦的处理了。在置换开始前,需要将异或运算的结果进行分组,从前往后分组即可,每组6Bit数据,分成8组。然后对每组进行分别处理,将前6Bit数据组编为1组,第7-12Bit数据编为2组,直到将最后6Bit数据编为8组。每一个组对应一个S盒表,第1组对应S1盒,第2组对应S2表,最后一组对应S8盒(这里说的S盒是一种特殊的置换表)。我们从F函数流程图可以看出,每一组数据在经过S盒置换后都会从6Bit数据变成了4Bit数据,它的置换过程比较特殊,所以我们先来观察下这8个S盒表的内容吧:
通过观察S盒我们发现,每一个S盒都是一个4*16矩阵,而且每个S盒中的内容都是可以用4个Bit为来表示的,嘿嘿这里就和输出的4Bit数据扯上关系了。接下来我们就来看看每一组6Bit数据是如何定位其对应的S盒数据的,我们这里把每组数据的第一个Bit为称作最重要位(MSB),把每组数据的最后一个Bit位称作最不重要位(LSB),之所以怎么取名是和他们的权重有关,第一位的权重最高,最后一位的权重最低。我们把每组数据的最重要位和最不重要位进行组合,形成一个2个Bit位的数据,这个数据表示对应S盒的行号。将中间四位数据进行组合形成一个4个Bit位的数据,这个数据表示对应S盒的列号。
定位S盒数据图解:
在定位了S盒位置后,就可以取得对应位置的数据,该数据是一个整数,我们必须把这个整数转换成4个二进制数据(转换方法见文末),这样就会得到这一组6Bit数据在经过S盒置换后的4Bit结果。因为一共由8组数据需要进行S盒置换,所以将所有4Bit数据合并后就会得到一个32位Bit数据。合并的方法和分组时的方法类似。
代码演示:
P置换和初始IP置换类似,只是打乱数据的位置,因此这里就不过多叙述,直接上图和置换代码:
代码演示:
F函数介绍完毕!
当R组数据经F函数处理后,接下来的过程就非常简单了,我们就在这里简单的介绍下。用F函数输出的32位数据与L[i]组数据进行按位异或运算,得到32位的运算结果,最后把这个处理结果赋值给R[i+1],R[i]组32位数据原封不动的赋值给L[i+1]。至此就完成的DES算法的一轮操作。当经过16轮一样的处理后就会得到最后一组数据L[16]、R[16]。对应的公式为:
代码演示:
最后就需要将L[16]、R[16],这两个分组的数据进行交叉合并,也就是将R[16]作为的前32位数据,将L[16]作为后32位数据,合并后就会得到一个64位数据。最后就是将这64位数据进行逆初始值置换处理,处理方式和初始值置换类似不过多叙述,直接上逆初始值置换表和对应的代码:
代码演示:
至此这64位数据经过逆初始值置换就和得到密文的二进制形式,最后就需要将这些二进制数据转换成16进制数据后输出就是最后想要的密文信息了。DES算法加密部分介绍完毕!
我想大家应该都注意到了把,在DES算法的循环处理中的F函数中由两个输入数据,一个是R组数据,另一个是子密钥数据。而且每一轮循环都要使用一个不同的子密钥,由16轮循环也就会由16个子密钥数据。我们这节就来讨论下这16个子密钥的生成算法。先来看下流程图:
通过观察流程图我们注意到一点,密钥刚开始是64位的,但是经过PC-1置换处理后就变成了56位了,这说明在密钥数据中有8位密钥参加子密钥的生成。到底是哪些位没有参加子密钥的生成呢?这里我们就要观察下PC-1置换表:
从表中观察我们发现,这个PC-1置换表是一个7*8矩阵。其中的内容缺少了8、16、24、32、40、48、56、64,也就是说这8个位置没有参加密钥的生成,他们被当作了校验位。如果没有对校验位做出相应的处理,就会出现多组密钥可以解密同一个加密密文的情况。比如:
我们可以发现,对相同的数据进行加密,使用不同的密钥生成的加密数据和解密结果都是相同的,而且解密结果正确。这个PC-1置换表也就导致了虽然输入的密钥是64位,但实际上只有56位起着作用。这里希望大家留意!
代码演示:
经过PC-1处理后就要进行数据分组了,这个分组的方法和DES算法的分组方法类似,都是前后分组,只不过每组数据都是28位的,前28位数据分给C组,后28位分给D组。这里也就不过多叙述了。
这节主要介绍每组数据的左移操作。在右移前我们把数据分成了两组,所以这里的左移是将两组数据分别进行左移处理,而且左移的位数相同。比如:将C组左移2位,就要把所有数据向左移动两位,并把前面超出范围的两位数据移动到数据的最后面。当然每组左移的位数也不是随意确定的,当i=1、2、9、16轮中,C、D两组向左移动一位;在其他轮中,C、D两组向左移动两位(i>=1)。这里需要注意的是,如果把所有移动位数加起来刚好等于每组数据的位数,也就是一共需要移动28位。也就是移动前的数据等于最后移动后的数据:C[0] = C[16]; D[0] = D[16];在这里我们就把移动的位数做成一共移位表,方便编程。
代码演示:
**在进行PC-2置换前,需要将C、D两组左移后的数据进行合并,合并方法与前方类似。C、D两组合并后形成一个56Bit数据块,这个数据块经过PC-2置换后就会变成一个48Bit的子密钥。通过如此这般16轮循环处理就可以形成16个48位的子密钥,这就是子密钥的生成过程。这里直接上PC-2置换表和代码。
代码演示:
密钥生成算法结束!至此DES算法的加密算法介绍完毕。
当我们了解了DES的加密算法后,它的解密算法也就大致明白了。为什么这么说呢?是因为与加密相比解密的方法就是将生成的16组子密钥按照相反的顺序输入到F函数即可完成解密,比如:加密时子密钥输入的顺序分别是1-16组,解密的顺序就应该是16-1组了。只不过对数据处理上有些许不同,这种差别会在附件的参考代码中体现处来。
在我们把输入数据当作二进制信息进行处理时,首先不是思考使用什么方法处理这些数据,而是一共思考这些数据应该如何转换成二进制信息。因为二进制的转换形式非常多,每个人对数据的处理也都会不同,这里举个例子:以0x34数据为例,可以把它转换成0011 0100,因为这样解析数据很直观;也可以把她转换成0010 1100,这就是将这个数据按照地址进行解析,从最不重要位到最重要位。这里也就没有什么对与错,不过我一直认为应该按照第二种方式进行转换,因为这种方式是最符合计算机处理逻辑的。但是本文为了可以得到与公开加密工具相同的结果,阅读了很多代码发现,他们都是按照第一种方式进行转换的。所以本文的参考代码也只好如此了。这里还有一个问题就是,为什么在DES加密框架中的分组不是左右分组而是前后分组。如果标准代码不是左右分组,和书上的介绍就会不一致,会不会误导读者呢?希望大佬们给予解答,谢谢。最后附上两种数据转换方式。
代码演示:
对以上问题的解答:(1)为什么分组叫左右分组,而不是前后分组?因为在以前DES算法输入的64位明文数据,不是用64个Char类型组合的,而是由2个int类型组成的,左分组表示int[0],右分组表示int[1]。这就是左右分组。(2)密码算法对数据的处理:密码算法对数据默认是将数据解析成大端对齐的,而不是小端对齐。所以0x34应该被解析成0011 0100b。
我们都知道在DES算法中,S盒是一个三维的数组,分别表示选择的S盒编号、S盒的行号、S盒的列号。我们可以从S盒的定位方式上发现,在获取S盒的行号和列号时,和其他的表不一样,而且非常特殊。行号时由6Bit数据中的最重要位和最不重要位决定的,列号是由6Bit数据的中间4位决定的。这种定位虽然看起来比较高大上,可能对加密算法的安全性有所提高,但却又有些多余。如果我们把每个S盒中的数据顺序换一下,让它按照顺序排列。这样可以省区计算行号列号的时间,可以直接通过输入6Bit的下标获取S盒的数据。
我们以第一个S盒为例,它是一个4行16列的表,当我们输入的6Bit数据为000000b时,选择的行号为0,列号为0,对应的数值为14。当我们输入000001b时,选择的行号为1,列号为0,对应的数值为0。当我们输入的6Bit数据为000010b时,选择的行号为0,列号为1,对应的数值为4。当我们输入000011b时,选择的行号为1,列号为1,对应的数值为15.....
如果我们按照这个顺序排列形成一个新的S1盒就可以直接使用输入的6Bit数据来获取其对应的数值了,这可以省区计算行号列号的操作。那么接下来我们就可以考虑如何获取新的S1盒内容了,总不能一个一个的找吧!我们可以设计一个循环,循环64次,通过这个循环就可以将S1盒的内容全部打印下来了。
代码演示
得到的新S1盒:
我们就用这种方法将剩余的新S盒全部打印出来,就可以得到8个不需要计算行号和列号的新S盒。也就是将S盒从三维数组转换成了二维数组,这个二维数组每个下标分别表示S盒编号、S盒的下标。现在将新S盒替换到以前的程序中,修改对应F函数就会完成对S盒的优化了。
新的F函数:
通过上一步我们可以直接从输入的6Bit索引值获取到对应的S盒4Bit数值。我们需要把8个4Bit数值合并成32位数据,这也就是P置换表的输入数据。我们这一步的目的就是要省去P表的置换处理,要达到这一目的我们就要找到S盒与P置换表的对应关系,通过输入6Bit数据直接获得该6Bit数据P置换后的结果,然后将8个置换后的结果相与就会得到最终的结果。这就就是优化的思路。
我们知道S1盒的输出数据位于所有32位数据的1-4Bit位。S2盒的输出位于所有数据的5-8Bit位。S3盒的输出位于所有数据的9-12Bit位......然后我们观察P置换表发现,S1盒的输出结果最终会位于所有32位数据的第9、17、23、31位。我们以S1盒输出的4Bit数据为1110b其他S盒结果为0来举例,1110b通过P置换后,高位的3个1会被移动到32位数据的第9、17、23位,最低位的0会被移动到第31位。最终会得到置换结果0x00410100。这样我们就找到了新S盒与P置换表的对应关系,新S1盒的索引0对应的结果位14,经P置换后结果为0x00410100。也就是我们在将来的SP1表输入000000b,将会输出0x00410100。
找到了对应关系,我们就要计算新的SP表中的数据了,我们使用以下方法来打印SP1表中的数据,使用应该for循环,循环64次,获取新S1盒的内容,然后将这个内容进行移位操作,就会获得SP1表。
代码演示
打印结果:
我们就可以通过这种方法将所有的SP盒内容打印出来。有了合并后的SP盒我们就应该修改下以前的流程图了。新的流程图如下:
通过这个新的流程图我们就该对F函数做修改了,修改后的结果如下:
优化一介绍完毕!其他优化等待后续跟新。。。。
优化代码采用Richard Outerbridge的D3DES(V5.09)中的位运算方法,取代初始置换过程。由于作者的计算方法过于精简,所以通过图解的方式进行演示。通过图解笔者发现了其代码中存在了快30年的问题,并附上正确的解决方案。
对应代码块:
对应代码块:
对应图解:
对应代码块:
对应图解:
对应代码块:
对应图解:
对应代码块:
对应图解:
对应代码块:
对应图解:
重点:在最后一个代码上,大家可能发现leftt这个数据块已经和初始置换表上的前32位一致了,然而最后一句代码竟然把leftt完美的移位给破坏了,而right数据块还是与初始置换表的后32位有点差距的。所以我们就找到了这个代码存在了数十年的问题,leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL;这句话应该用于处理right数据块,而不是处理leftt。那要怎么改呢?我们从图解中发现,如果将right的最后一位移动到第一位上,那么存在置换表的替换就完成了。所以改动后的最后一句代码如下:
在进行初始置换前,作者还将输入的明文数据进行了由小端序转换成大端序的处理,所以如果要彻底替换初始置换,还需要进行大小端序转换,然后进行初始置换的移位操作,最后不要忘了将大端序转换成小端序。
附上修改后的加密函数:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-2-25 23:21
被QiuJYu编辑
,原因: 图片修正