购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 分组加密算法

分组加密算法又称块加密算法,顾名思义,是一组一组进行加解密的。它将明文分成多个等长的块(Block,或称分组),使用确定的算法和对称密钥对每组分别加解密。通俗地讲,就是一组一组地进行加解密,而且每组数据长度相同。

3.4.1 工作模式

有人或许会想,既然是一组一组地进行加解密的,那程序是否可以设计成并行加解密呢?比如多核计算机上开n个线程同时对n个分组进行加解密。这个想法不完全正确。因为分组和分组之间可能存在关联。这就引出了分组算法的工作模式概念。分组算法的工作模式就是用来确定分组之间是否有关联以及如何关联的。不同的工作模式(也称加密模式)使得每个加密区块(分组)之间的关系不同。

通常,分组算法有5种工作模式,如表3-1所示。

表3-1 分组算法的5种工作模式

1.ECB模式

ECB模式是最早采用的简单模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。相同的明文会产生相同的密文。其缺点是:电子密码本模式用一个密钥加密消息的所有块,如果原消息中重复明文块,那么加密消息中的相应密文块也会重复。因此,电子密码本模式适合加密小消息。ECB模式的具体过程如图3-7所示。

图3-7

图3-7中,每个分组的运算(加密或解密)都是独立的,每个分组加密只需要密钥和明文分组即可,每个分组解密也只需要密钥和密文分组即可。这就产生了一个问题,即加密时相同内容的明文块将得到相同的密文块(密钥是相同的,输入也是相同的,得到的结果也就相同),这样就难以抵抗统计分析攻击了。当然,ECB每组没关系也是其优点,比如有利于并行计算,误差不会被传送,运算简单,不需要初始向量(Initialization Vector,IV)。

该模式的特点:简单、快速,加密和解密过程支持并行计算;明文中的重复排列会反映在密文中;通过删除、替换密文分组可以对明文进行操作(可攻击),无法抵御重放攻击;对包含某些比特错误的密文进行解密时,对应的分组会出错。

2.CBC模式

首先认识一下初始向量。初始向量(或称初向量)是一个固定长度的比特串。一般使用时会要求它是随机数或伪随机数。使用随机数产生的初始向量,使得同一个密钥加密的结果每次都不同,这样攻击者难以对同一把密钥的密文进行破解。

CBC模式由IBM于1976年发明。加密时,第一个明文块和初始向量进行异或后,再用key进行加密,以后每个明文块与前一个分组结果(密文)块进行异或后,再用key进行加密。解密时,第一个密文块先用key解密,得到的中间结果再与初始向量进行异或后得到第一个明文分组(第一个分组的最终明文结果),后面每个密文块也是先用key解密,得到的中间结果再与前一个密文分组(注意是解密之前的密文分组)进行异或后得到本次明文分组。在这种方法中,每个分组的结果都依赖于它前面的分组。同时,第一个分组也依赖于初始向量,初始向量的长度和分组相同。但要注意的是,加密时的初始向量和解密时的初始向量必须相同。

CBC模式需要初始向量(长度与分组大小相同)参与计算第一组密文,第一组密文当作向量与第二组数据一起计算后再进行加密,产生第二组密文,后面以此类推,如图3-8所示。

图3-8

CBC是常用的工作模式。它的主要缺点在于加密过程是串行的,无法被并行化(因为后一个运算要等到前一个运算的结束后才能开始)。另外,明文中的微小改变会导致其后的全部密文块发生改变,这是其又一个缺点:加密时可能会有误差传递。

而在解密时,因为是把前一个密文分组作为当前向量,因此不必等前一个分组运算完毕,所以解密时可以并行化,解密时密文中一位的改变只会导致其对应的明文块发生改变以及下一个明文块中的对应位(因为是异或运算)发生改变,不会影响其他明文的内容,所以解密时不会产生误差传递。

该模式的特点:明文的重复排列不会反映在密文中;只有解密过程可以并行计算,加密过程由于需要前一个密文组,因此无法进行并行计算;能够解密任意密文分组;对包含某些错误比特的密文进行解密,第一个分组的全部比特(“全部”是由于密文参与了解密算法)和后一个分组的相应比特会出错(“相应”是由于出错的密文在后一组中只参与了XOR运算);填充提示攻击。

3.CFB模式

CFB(Cipher Feedback,密文反馈)模式和CBC模式类似,也需要初始向量。加密第一个分组时,先对初始向量进行加密,得到的中间结果再与第一个明文分组进行异或得到第一个密文分组;加密后面的分组时,把前一个密文分组作为向量先加密,得到的中间结果再与当前明文分组进行异或得到密文分组。解密第一个分组时,先对初始向量进行加密运算(注意,用的是加密算法),得到的中间结果再与第一个密文分组进行异或得到明文分组;解密后面的分组时,把上一个密文分组当作向量进行加密运算(注意,用的还是加密算法),得到的中间结果再与本次的密文分组进行异或得到本次的明文分组。过程如图3-9所示。

图3-9

同CBC模式一样,加密时因为要等前一次的结果,所以只能串行,无法并行计算。解密时因为不用等前一次的结果,所以可以并行计算。

该模式的特点:不需要填充;仅解密过程支持并行计算,加密过程由于需要前一个密文组参与,无法进行并行计算;能够解密任意密文分组;对包含某些错误比特的密文进行解密,第一个分组的部分比特和后一个分组的全部比特会出错;不能抵御重放攻击。

4.OFB模式

OFB(Output Feedback,输出反馈)模式也需要初始向量。加密第一个分组时,先对初始向量进行加密,得到的中间结果再与第一个明文分组进行异或得到第一个密文分组;加密后面的分组时,把前一个中间结果(前一个分组的向量的密文)作为向量先加密,得到的中间结果再与当前明文分组进行异或得到密文分组。解密第一个分组时,先对初始向量进行加密运算(注意用的是加密算法),得到的中间结果再与第一个密文分组进行异或得到明文分组;解密后面的分组时,把上一个中间结果(前一个分组的向量的密文,因为用的依然是加密算法)当作向量进行加密运算(注意用的是加密算法),得到的中间结果再与本次的密文分组进行异或得到本次的明文分组。过程如图3-10和图3-11所示。

图3-10

图3-11

该模式的特点:不需要填充;可事先进行加密、解密准备;加密、解密使用相同的结构(加密和解密算法过程相同);对包含某些错误比特的密文进行解密时,只有明文中相应比特会出错;不支持并行计算。

3.4.2 短块加密

分组密码一次只能对一个固定长度的明文(密文)块进行加(解)密。当最后一次要处理的数据小于分组长度时,我们就要进行特殊处理。这里把长度小于分组长度的数据称为短块。短块因为不足一个分组,因此不能直接进行加解密,必须采用合适的技术手段解决短块加解密问题。比如,要加密33个字节,前面32个字节是16的整数倍,可以直接加密,剩下的1个字节就不能直接加密了,因为不足一个分组长度了。

对于短块的处理,通常有3种技术方法:

(1)填充技术

填充技术就是用无用的数据填充短块,使之成为标准块(长度为一个分组的数据块)。填充的方式可以自定义,比如填充0、填充数据的长度值和随机数等。严格来讲,为了确保加密强度,填充的数据应是随机数。但是收信者如何知道哪些数字是填充的呢?这就需要增加指示信息,通常用最后8位作为填充指示符,比如最后一个字节存放填充的数据的长度。

值得注意的是,填充可能引起存储器溢出,因而可能不适合文件和数据块加密。填充加密后,密文长度跟明文长度不一样。

(2)密文挪用技术

这种技术不需要引入新数据,只需把短块和前面分组的部分密文组成一个分组后进行加密。密文挪用法也需要指示挪用位数的指示符,否则收信者不知道挪用了多少位,从而不能正确解密。密文挪用法的优点是不引起数据扩展,也就是密文长度同明文长度是一致的。缺点是控制稍复杂。

(3)序列加密

对于最后一块短块数据,直接使用密钥K与短块数据模2相加。序列加密技术的优点是简单,但若短块太短,则加密强度不高。

3.4.3 DES和3DES算法

1.DES概述

DES是IBM公司研制的一种对称算法,也就是说它使用同一个密钥来加密和解密数据,并且加密和解密使用的是同一种算法。美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准。DES还是一种分组加密算法,该算法每次处理固定长度的数据段,称之为分组。DES分组的大小是64位(8字节),如果加密的数据长度不是64位的倍数,可以按照某种具体的规则来填充位。DES算法的保密性依赖于密钥,保护密钥非常重要。

DES加密技术是一种常用的对称加密技术,该技术算法公开,加密强度大,运算速度快,在各行业甚至军事领域得到了广泛的应用。DES算法从1977年公布到现在已有40多年的历史,虽然有些人对它的加密强度持怀疑态度,但现在还没有发现实用的破译DES算法的方法。并且人们在应用中不断提出新的方法增强DES算法的加密强度,如3重DES算法、带有交换S盒的DES算法等。因此,DES算法在信息安全领域仍广泛地应用。

2.DES算法的密钥

严格来讲,DES算法的密钥长度为56位,但通常用一个64位的数来表示密钥,然后经过转换得到56位的密钥,而第8、16、24、32、40、48、56、64位是校验位,不参与DES加解密运算,所以这些位上的数值不能算密钥。为了方便区分,我们把64位的数称为从用户处取得的用户密钥,而56位的数称为初始密钥、工作密钥或有效输入密钥。

DES算法的安全性首先取决于密钥的长度。密钥越长,破译者利用穷举法搜索密钥的难度就越大。目前,根据当今计算机的处理速度和能力,56位长度的密钥已经能够被破解,而128位的密钥则被认为是安全的,但随着时间的推移,这个数字也迟早会被突破。

具体加解密运算前,DES算法的密钥还要通过等分、移位、选取、迭代形成16个子密钥,分别供每一轮运算使用,每个长48比特。计算出子密钥是进行DES加密的前提条件。生成子密钥的基本步骤如下:

(1)等分

等分密钥就是从用户处取得一个64位长的初始密钥变为56位的工作密钥。方法很简单,根据一个固定“站位表”让64位初始密钥中的对应位置的值出列,并“站”到表中去。图3-12所示的表格中的数字表示初始密钥的每一位的位置,比如57表示初始密钥中第57位的比特值要站到该表的第1个位置上(初始密钥的第57位成为新密钥的第1位),49表示初始密钥中第49位的比特值要站到该表的第2个位置上(初始密钥的第49位成为新密钥的第2位),从左到右、从上到下依次进行,直到初始密钥的第4位成为新密钥的最后一位。

图3-12

比如,我们现在有一个64位的初始密钥:K=133457799BBCDFF1,转化成二进制:

K = 00010011 00110100 01010111 01111001 10011011 10111100 11011111 11110001

根据图3-12,我们将得到56位的工作密钥:

K w 一共56位。细心的朋友会发现,图3-12中没有数字8、16、24、32、40、48、56、64。的确如此,这些位置去掉了,所以工作密钥K w 是56位。等分工作结束,进入下一步。

(2)移位

我们通过上一步的等分工作得到了一个工作密钥:

将这个密钥拆分为左右两部分:C 0 和D 0 ,每半边都有28位。

比如,对于K w ,我们得到:

对相同定义的C 0 和D 0 ,我们现在创建16个块C n 和D n ,1≤n≤16。每一对C n 和D n 都是由前一对 移位而来的。具体来说,对于n = 1, 2, …, 16,在前一轮移位的结果上,使用表3-2进行一些次数的左移操作。什么叫左移?左移指的是将除第一位外的所有位往左移一位,将第一位移动至最后一位。

表3-2 进行左移操作

也就是说,C 3 和D 3 是C 2 和D 2 移位而来的;C 16 和D 16 则是由C 15 和D 15 通过一次左移得到的。在所有情况下,一次左移就是将所有比特往左移动一位,使得移位后的比特的位置相较于变换前成为2, 3,…, 28, 1。比如,对于原始子密钥C 0 和D 0 ,我们得到:

现在就可以得到第n轮的新密钥K n (1≤n≤16)了。具体做法是,对每对拼合后的临时子密钥C n D n 按表3-3执行变换。

表3-3 对每对拼合后的临时子密钥C n D n 执行变换顺序

每对临时子密钥有56位,但表3-2仅仅使用其中的48位。该表格的数字同样表示位置,让每对临时子密钥相应位置上的比特值站到该表格中去,从而形成新的子密钥。于是,第n轮的新子密钥K n 的第1位来自组合的临时子密钥C n D n 的第14位,第2位来自第17位,以此类推,直到新密钥的第48位来自组合密钥的第32位。比如,对于第1轮的组合的临时子密钥,我们有:

通过表3-3的变换后,得到:

通过表3-3,我们可以让56位的长度变为48位。同理,对于其他密钥得到:

16组子密钥全部生成完毕,它们整装待发,可以进入实际加解密运算了。为了更形象地展示上述子密钥的生成过程,我们画了一张图来帮助大家理解,如图3-13所示。

图3-13

左旋1位的意思就是循环左移1位。

3.DES算法的原理

16个子密钥已经全部生成完毕,下面正式进行加解密。DES算法是分组算法,每组8字节,加密时一组一组进行加密,解密时也是一组一组进行解密。

要加密一组明文,每个子密钥按照顺序(1~16)以一系列的位操作施加于数据上,每个子密钥一次,一共重复16次。每一次迭代称为一轮。要对密文进行解密可以采用同样的步骤,只是子密钥是按照逆向的顺序(16~1)对密文进行处理的。

我们先来看加密,首先要对某个明文分组M进行初始变换(Initial Permutation,IP),变换依然是通过一张表格,让明文出列站到表格上去,如表3-4所示。

表3-4 对某个明文分组M进行初始变换顺序

表格的下标对应新数据的下标,表格的数值x表示新数据的这一位来自旧数据的第x位。参照表3-3,M的第58位成为IP的第1位,M的第50位成为IP的第2位,M的第7位成为IP的最后一位。比如,假设明文分组M数据为:

M = 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

对M的区块执行初始变换,得到新数据:

IP = 1100 1100 0000 0000 1100 1100 1111 1111 1111 0000 1010 1010 1111 0000 1010 1010

这里M的第58位是1,变成了IP的第1位。M的第50位是1,变成了IP的第2位。M的第7位是0,变成了IP的最后一位。初始变换完成。

接着把初始变换后的新数据IP分为32位的左半边L 0 和32位的右半边R 0

我们接着执行16个迭代,迭代过程就是:对于1≤n≤16,使用函数f,函数f输入两个区块,一个32位的数据区块和一个48位的密钥区块K n ,输出一个32位的区块。定义符号⊕表示异或运算。那么让n从1循环到16,我们计算:

这样就得到了最终区块,也就是n=16的L 16 R 16 。这个过程就是拿前一个迭代结果的右边32位作为当前迭代左边32位。对于当前迭代的右边32位,将它和上一个迭代的f函数的输出执行XOR运算。

比如,对于n=1,我们有:

剩下的就是f函数的工作了。为了计算f,我们首先拓展每个 ,将其从32位拓展到48位。这是通过一张表来重复 中的一些位来实现的。这张表如图3-14所示。

图3-14

我们用一个函数E来表示这个过程。也就是说,函数 输入32位,输出48位。比如,给定R 0 ,我们可以计算出E(R 0 ) :

注意,输入的每4位一个分组被拓展为输出的每6位一个分组。接着在f函数中,对输出的 和密钥K n 执行XOR运算:

比如,对于K 1 ⊕E(R 0 ),我们有:

到这里,还没有完成f函数的运算,我们仅仅使用一张表将 从32位拓展为48位,并且对这个结果和密钥K n 执行了异或运算。现在有了48位的结果,或者说8组6比特数据。我们现在要对每组的6比特执行一些奇怪的操作:将它作为一张被称为“S盒”的表格的地址。每组6比特都将给我们一个位于不同S盒中的地址,在那个地址里存放着一个4比特的数据,这个4比特的数据将会替换掉原来的6比特。最终结果就是,8组6比特的数据被转换为8组4比特(一共32位)的数据。

将上一步的48位的结果写成如下形式:

每个B i 都是一个6比特的分组,我们现在计算S 1 (B 1 )S 2 (B 2 )S 3 (B 3 )S 4 (B 4 )S 5 (B 5 )S 6 (B 6 )S 7 (B 7 )S 8 (B 8 ),其中,S i (B i )指的是第i个S盒的输出。为了计算每个S函数S 1 ,S 2 ,…, S 8 ,取一个6位的区块作为输入,输出一个4位的区块。决定S 1 的表格如图3-15所示。

图3-15

如果S 1 是定义在这张表上的函数,B是一个6位的块,那么计算S 1 (B)的方法是:B的第一位和最后一位组合起来的二进制数决定一个介于0和3之间的十进制数(或者二进制数00到11之间),设这个数为i。B的中间4位二进制数代表一个介于0到15之间的十进制数(二进制数0000到1111),设这个数为j。查表找到第i行第j列的那个数,这是一个介于0和15之间的数,并且它能由一个唯一的4位区块表示。这个区块就是函数S 1 输入B得到的输出S 1 (B)。比如,输入B=011011,第一位是0,最后一位是1,决定了行号是01,也就是十进制的1。中间4位是1101,也就是十进制的13,所以列号是13。查表第1行第13列得到数字5。这决定了输出,5是二进制0101,所以输出就是0101,即S1(011011) = 0101。

同理,定义这8个函数S 1 ,…,S 8 的表格如图3-16所示。

图3-16

对于第一轮,我们得到这8个S盒的输出:

函数f的最后一步就是对S盒的输出进行一个变换来产生最终值:

其中,变换P由表3-5定义。P输入32位数据,通过下标产生32位输出。

表3-5 变换P的定义

比如,对于8个S盒的输出:

我们得到:

f = 0010 0011 0100 1010 1010 1001 1011 1011

那么:

在下一轮迭代中,L 2 = R 1 ,这就是我们刚刚计算的结果。之后必须计算R 2 =L 1 + f(R 1 , K 2 ),一直完成16个迭代。在第16个迭代之后,我们有了区块L 16 和R 16 。接着逆转两个区块的顺序得到一个64位的区块:R 16 L 16 ,然后对其执行一个最终的变换IP-1,其定义如表3-6所示。

表3-6 最终的变换定义表

也就是说,该变换输出的第1位是输入的第40位,输出的第2位是输入的第8位,一直到将输入的第25位作为输出的最后一位。

比如,如果使用上述方法得到了第16轮的左右两个区块:

我们将这两个区块调换位置,然后执行最终变换:

写成16进制得到:85E813540F0AB405。

这就是明文M = 0123456789ABCDEF的加密形式C = 85E813540F0AB405。

解密就是加密的反过程。执行上述步骤,只不过在16轮迭代中,调转左右子密钥的位置而已。

4.3DES

DES是一个经典的对称加密算法,但缺陷也很明显,即56位的密钥安全性不足,已被证实可以在短时间内破解。为了解决此问题,出现了3DES(也称Triple DES)。3DES为DES向AES过渡的加密算法,它使用3条56位的密钥对数据进行三次加解密。为了兼容普通的DES,3DES加密并没有直接使用“加密→加密→加密”的方式,而是采用了“加密→解密→加密”的方式。当三重密钥均相同时,前两步相互抵消,相当于仅实现了一次加密,因此可实现对普通DES加密算法的兼容。

3DES解密过程与加密过程相反,即逆序使用密钥,以密钥3、密钥2、密钥1的顺序执行“解密→加密→解密”。

设E k ()和D k ()代表DES算法的加密和解密过程,k 1 、k 2 、k 3 代表DES算法使用的密钥,P代表明文,C代表密文。这样,3DES加密过程为:C=Ek 3 (Dk 2 (Ek 1 (P))),即先用密钥k 1 做DES加密,再用k 2 做DES解密,再用k 3 做DES加密。3DES解密过程为:P=Dk 1 ((Ek 2 (Dk 3 (C))),即先用k 3 加密,再用k 2 做DES加密,再用k 1 做DES解密。这里可以k 1 =k 3 ,但不能k 1 =k 2 =k 3 (如果相等的话就成了DES算法,因为三次里面有两次DES使用相同的密钥进行加解密,从而抵消掉了,等于没做,只有最后一次DES起了作用)。3DES算法如图3-17所示。

图3-17

这里,我们给出3DES加密的伪代码:

其中,SubKey是16圈子密钥,全局定义如下:

bool SubKey[2][16][48];

3DES的解密伪代码如下:

具体实现稍后会给出实例。相比DES,3DES因密钥长度变长,安全性有所提高,但其处理速度不高。因此又出现了AES加密算法,AES相较于3DES速度更快、安全性更高。

至此,我们对DES和3DES算法的原理阐述完毕,下面进入实战。

5.DES和3DES算法实现

纸上得来终觉浅,绝知此事要躬行。前面讲了不少DES算法的原理,现在我们将在VC 2017下进行实现。代码稍微有点长,但笔者对关键代码都做了注释,结合前面的原理来看,相信大家能看得懂。

【例3.4】实现DES算法(C语言版)

(1)打开VC 2017,新建一个控制面板工程,工程名是test。

(2)打开test.cpp,输入代码如下:

(3)保存工程并运行,运行结果如图3-18所示。

图3-18

以上是我们从零开始实现的DES算法,这对学习理解来讲是非常重要的过程。但一线工作中,很多时候是不需要重复造轮子的,比如可以使用现成的库。下面我们用OpenSSL来实现DES算法。该例子中,我们用ECB工作模式,所以不需要初始向量。OpenSSL中提供了函数DES_ecb_encrypt来实现ECB模式的DES算法,该函数声明如下:

其中,参数input指向输入缓冲区,加密时表示明文,解密时表示密文;output表示输出缓冲区,加密时表示密文,解密时表示明文;ks指向密钥缓冲区;enc表示加密还是解密。

这个密钥结构ks看起来有点怪,其实它是通过其他函数转换而来的,比如下面的代码片段:

下面我们来看具体例子。

【例3.5】实现DES算法(OpenSSL版)

(1)打开VC 2017,新建一个控制面板工程,工程名是test。

(2)打开工程属性,在“C/C++”→“附加包含目录”旁输入头文件路径:D:\openssl-1.1.1b\win32-debug\include,然后在“链接器”→“常规”→“附加库目录”旁输入静态库路径:D:\openssl-1.1.1b\win32-debug\lib,再在“链接器”→“输入”→“附加依赖项”旁增加三个库名:ws2_32.lib;Crypt32.lib;libcrypto.lib;,注意每个库名之间用英文分号隔开。单击“确定”按钮关闭对话框。

(3)下面开始添加代码,在工程中打开test.cpp,输入代码如下:

(4)保存工程并运行,运行结果如图3-19所示。

图3-19

3.4.4 SM4算法

1.概述

随着密码标准的制定活动在国际上热烈开展,我国对密码算法的设计与分析也越来越关注,因此国家密码管理局公布了国密算法SM4。SM4算法的全称为SM4分组密码算法,是国家密码管理局于2012年3月发布的第23号公告中公布的密码行业标准。该算法适用于无线局域网的安全领域。SM4算法的优点是软件和硬件实现容易,运算速度快。

SM4分组密码算法是一个迭代分组密码算法,由加解密算法和密钥扩展算法组成。SM4分组密码算法采用非平衡Feistel结构,明文分组长度为128比特,密钥长度为128比特。加密算法与密钥扩展算法都采用32轮非线性迭代结构。解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。

与DES类似,SM4算法是一种分组密码算法。其分组长度为128比特,密钥长度也为128比特。这里要解释一下分组长度和密钥长度。所谓分组长度,就是一个信息分组的比特位数。而密钥长度是密钥的比特位数。可以看出,这两个长度都是比特位数。当然,我们平时说16字节也可以。但如果看到分组长度是128,没有带单位,那么应该默认是比特。

SM4加密算法与密钥扩展算法均采用32轮非线性迭代结构,以字(32位)为单位进行加密运算,每一次迭代运算均为一轮变换函数F。

SM4分组密码算法在使用上表现出了安全高效的特点,与其他分组密码相比较有以下优势:

(1)算法资源利用率高,表现为密钥扩展算法与加密算法可共享。

(2)加密算法流程和解密算法流程一样,只是轮密钥顺序相反,因此无论是软件实现还是硬件实现都非常方便。

(3)算法中包含异或运算、数据的输入输出、线性置换等模块,这些模块都是按8位来进行运算的,现有的处理器完全能处理。

SM4分组密码算法主要包括加密算法、解密算法以及密钥的扩展算法三部分。其基本算法结构如图3-20所示。

图3-20

可见,其最初输入的128位密钥还要进行密钥扩展,变成轮密钥后才能用于算法(轮函数)。

2.密钥

SM4算法中的加密密钥和解密密钥长度相同,一般定为128比特,即16字节,在算法中表示为MK=(MK 0 ,MK 1 ,MK 2 ,MK 3 ),其中MK i (i=0,1,2,3)为32比特。而算法中的轮密钥是由加密算法的密钥生成的,主要表示为(rk 0 ,rk 1 ,…,rk 31 ),其中rk i (i=0,1,…,31)为32比特。

FK=(FK 1 ,FK 2 ,FK 3 ,FK 4 )为系统参数,CK=(CK 0 ,CK 1 ,…,CK 31 )为固定参数,这两个参数主要在密钥扩展算法中使用,其中FK i (i=0,1,…,31),CK i (i=0,1,…,31),均为32比特,也就是说一个FK i 和一个CK i 都是4字节。

3.密钥扩展算法

SM4分组密码算法使用128位的加密密钥,加密算法与密钥扩展算法都采用32轮非线性迭代结构,每一轮加密使用一个32位的轮密钥,共使用32个轮密钥。因此,需要使用密钥扩展算法从加密密钥中产生出32个轮密钥。轮密钥由加密密钥通过密钥扩展算法生成。轮密钥生成方法为:

设输入加密密钥为MK= (MK 0 , MK 1 , MK 2 , MK 3 ),其中MK i (i=0,1,2,3)为32位,也就是一个MK i 有4字节。输出轮密钥为(rk 0 ,rk 1 ,…,rk 31 ),其中rk i (i=0,1,…,31)为32位,也就是一个rk i 有4字节。中间数据为K i (i=0,1,…,34,35)。密钥扩展算法可描述如下:

第一步,计算K 0 、K 1 、K 2 、K 3

也就是加密密钥分量和固定参数分量进行异或。

第二步,计算后续K i 和每个轮密钥rk i

说明:

(1)T'变换与加密算法轮函数(后面会讲到)中的T基本相同,只是将其中的线性变换L修改为以下的L';

L'(B)=B⊕(B<<<13)⊕(B<<<23)

(2)系统参数FK的取值为:

(3)固定参数CK的取值方法为:

设ck i,j 为CK i 的第j字节(i=0,1,…,31,j=0,1,2,3),即CK i =(ck i,0 ,ck i,1 ,ck i,2 ,ck i,3 ),则ck i,j =(4i+j)×7(mod 256)。

固定参数CK i (i=0,1,2,…,31)的具体值为:

00070E15,1C232A31,383F464D,545B6269,
70777E85,8C939AA1,A8AFB6BD,C4CBD2D9,
E0E7EEF5,FC030A11,181F262D,343B4249,
50575E65,6C737A81,888F969D,A4ABB2B9,
C0C7CED5,DCE3EAF1,F8FF060D,141B2229,
30373E45,,4C535A61,686F767D,848B9299,
A0A7AEB5,BCC3CAD1,D8DFE6ED,F4FB0209,
10171E25,2C333A41,484F565D,646B7279。

4.轮函数

在具体介绍SM4加密算法之前,要先介绍一下轮函数,也就是加密算法中每轮所使用的函数。

设输入为 表示所属数据是二进制形式的,每部分是32位,一共4部分。轮密钥为 ,则轮函数F为:

这就是轮函数的结构,其中T叫作合成置换,它是可逆变换 ,由非线性变换τ和线性变换L复合而成,即T(·)=L(τ(·))。我们分别来看一下τ和L。

(1)非线性变换τ

非线性变换τ由4个S盒并行组成。假设输入的内容为 ,通过进行非线性变换,最后算法的输出结果为 ,即:

其中,Sbox数据定义如下:

一共256个数据,也可以定义为int s[256];。若输EF,则经S盒后的值为第E行和第F列的值,Sbox[0xE][0xF]=84。

(2)线性变换L

非线性变换τ的输出是线性变换L的输入。设输入为B∈z 2 (这里的B就是上面(1)中的B),输出为C∈Z2,则定义L的计算如下:

C=L(B)=B⊕(B<<<2)⊕(B<<<10)⊕(B<<<18)⊕(B<<<24)

⊕表示异或,<<<表示循环左移。至此,轮函数F已经计算成功。

5.加密算法

SM4分组密码算法的加密算法流程包含32次迭代运算以及一次反序变换,用R来表示反序变换。

假设明文输入为 表示所属数据是二进制形式的,每部分是32位,一共4部分。密文输出为 ,轮密钥为 ,i=0,1,…,31。加密算法的运算过程如下:

(1)32次迭代运算:X i+4 =F(X i ,X i+1 ,X i+2 ,X i+3 ,rk i ),i=0,1,…,31。其中,F是轮函数,前面介绍过了。

(2)反序变换:(Y 0 ,Y 1 ,Y 2 ,Y 3 )=R(X 32 ,X 33 ,X 34 ,X 35 )=(X 35 ,X 34 ,X 33 ,X 32 )。对最后一轮数据进行反序变换并得到密文输出。

SM4算法的整体结构如图3-21所示。

图3-21

6.解密算法

SM4分组密码算法的解密算法和加密算法一致,不同的仅是轮密钥的使用顺序。在解密算法中所使用的轮密钥为(rk 31 ,rk 30 ,…,rk 0 )。

7.SM4算法的实现

前面讲述了SM4算法的理论知识,现在我们要上机实现了。

【例3.6】实现SM4算法(16字节版)

(1)为什么叫16字节版呢?这是因为本例只能对16字节数据进行加解密。为什么不直接给出能对任意长度数据进行加解密的版本呢?这是因为任意长度加解密的版本也是以16字节版为基础的。别忘记了,SM4的分组长度是16字节,SM4是分组加解密的,任何长度的明文都会划分为16字节一组,然后一组一组地进行加解密。下例将演示任意长度的版本。

打开VC 2017,新建一个控制面板工程,工程名是test。

(2)首先来声明几个函数。在工程中添加一个sm4.h,输入代码如下:

其中,#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这个预处理指令,就能够保证头文件只被编译一次。

函数SM4_KeySchedule用来生成轮密钥,参数MK是输入参数,存放主密钥(也就是加密密钥);rk是输出参数,存放生成的轮密钥。

函数SM4_Encrypt是SM4加密函数,输入参数MK存放主密钥;输入参数PlainText存放要加密的明文;输出参数CipherText存放加密的结果,即密文。

函数SM4_Decrypt是SM4解密函数,输入参数MK存放主密钥,这个密钥和加密时的主密钥必须一样;输入参数CipherText存放要解密的密文;输出参数PlainText存放解密的结果,即明文。

函数SM4_SelfCheck是SM4自检函数,它用标准数据作为输入,那么输出也是一个标准结果,如果输出和标准结果不同,就说明发生错误了。若函数返回0,则表示自检成功,否则失败。

(3)开始实现这几个函数。首先定义一些固定数据。在工程中新建文件sm4.cpp,并定义两个全局数组SM4_CK和SM4_FK:

unsigned int SM4_CK[32] = { 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 };
unsigned int SM4_FK[4] = { 0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC };

其中,SM4_CK用来存放固定参数,SM4_FK用来存放系统参数,这两个参数都用于密钥扩展算法,也就是在SM4_KeySchedule中会用到。

然后添加一个全局数组作为S盒:

unsigned char SM4_Sbox[256] =
{ 0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48 };

至此,全局变量添加完毕。下面添加函数定义,首先添加生成轮密钥的函数:

该函数输入加密密钥,输出轮密钥。函数实现过程和前面的密钥扩展算法描述完全一致,对照着看完全能看懂。其实就是两步,第一步,计算K 0 、K 1 、K 2 、K 3 ;第二步,计算后续K i 和每个轮密钥rk i

下面再添加SM4加密函数:

该函数传入16字节的加密密钥MK和16字节的明文PlainText,得到16字节的密文CipherText。在函数中,首先调用SM4_KeySchedule来生成轮密钥,然后做32次迭代运算,最后一个for循环就是对最后一轮数据进行反序变换并得到密文输出。

下面再添加SM4解密函数:

该函数传入16字节的加密密钥MK和16字节的密文CipherText,得到16字节的明文PlainText。我们可以看出,解密过程和加密过程几乎一样,区别就在于在32次迭代运算中,轮密钥倒着开始用。

下面再添加SM4自检函数:

自检函数通常用标准明文数据、标准加密密钥数据作为输入,然后看运算结果是否和标准密文数据一致,如果一致就说明算法过程是正确的,否则表示出错。

最后打开test.cpp,添加main函数代码如下:

(4)保存工程并运行,运行结果如图3-22所示。

图3-22

至此,16字节的SM4加解密函数实现成功了。但该例子无法用于一线开发,因为一线开发中,不可能只有16字节数据需要处理。所以下面来实现一个支持任意长度的SM4加解密函数,并且实现4个分组模式(ECB、CBC、CFB和OFB),分组模式的概念在3.4节已经介绍过了,这里不再赘述。

【例3.7】实现SM4-ECB/CBC/CFB/OFB算法(大数据版)

(1)我们将在上例的基础上增加内容,使得本例能支持大数据的加解密。把上例复制一份,用VC 2017打开。

(2)在工程中打开sm4.h,添加3个宏定义:

再打开sm4.cpp,添加ECB模式的SM4算法如下:

SM4加解密的分组大小为128Bit,故对消息进行加解密时,若消息长度过长,则需要进行循环分组加解密。代码清楚明了,而且对代码进行了注释,相信能看懂。

再在sm4.cpp中添加CBC模式的SM4算法如下:

我们这个算法支持in和out指向同一个缓冲区(称为原地加解密),根据CBC模式的原理,加密时不必区分in和out是否相同,而解密时需要区分。

再在sm4.cpp中添加CFB模式的SM4算法如下:

注意,CFB模式和CBC类似,也需要IV。

再在sm4.cpp中添加OFB模式的SM4算法如下:

OFB模式的加密和解密是一致的。

4个工作模式的SM4算法实现完毕。为了让其他函数调用,我们在sm4.h中添加这4个函数的声明:

(3)在工程中新建一个C++源文件sm4check.cpp,我们将在该文件中添加SM4的检测函数,也就是调用前面实现的SM4加解密函数。首先添加sm4ecbcheck函数,代码如下:

代码中,我们首先用16字节的标准数据来测试sm4ecb,标准数据分别定义在key、plain和cipher中,key表示输入的加解密密钥,plain表示要加密的明文,cipher表示加密后的密文。我们通过调用sm4ecb加密后,把输出的加密结果地标准数据cipher进行比较,如果一致,就说明加密正确。在16字节验证无误后,我们又用长度为32、64、128、256、512、1024、2048和4096的数据进行了加解密测试,先加密,再解密,然后比较解密结果和明文是否一致。

再在sm4check.cpp中添加CBC模式的检测函数,代码如下:

在代码中,先用32字节的标准数据进行测试,标准数据分别定义在key、plain和cipher中,key表示输入的加解密密钥,plain表示要加密的明文,cipher表示加密后的密文。我们通过调用sm4cbc加密后,把输出的加密结果与标准数据cipher进行比较,如果一致,就说明加密正确。在32字节的标准数据验证无误后,我们又用长度为32、64、128、256、512、1024、2048和4096的数据进行了加解密测试,先加密,再解密,然后比较解密结果和明文是否一致。

再在sm4check.cpp中添加CFC模式的检测函数,代码如下:

我们用长度为16、32、64、128、256、512、1024、2048和4096的数据进行了CFB模式的加解密测试,先加密,再解密,然后比较解密结果和明文是否一致。

再在sm4check.cpp中添加OFB模式的检测函数,代码如下:

我们用长度为16、32、64、128、256、512、1024、2048和4096的数据进行了OFB模式的加解密测试,先加密,再解密,然后比较解密结果和明文是否一致。

至此,加解密的检测函数添加完毕。我们可以在main函数中直接调用它们了。

(4)在工程中打开test.cpp,添加检测函数声明:

extern int sm4ecbcheck();
extern int sm4cbccheck();
extern int sm4cfbcheck();
extern int sm4ofbcheck();

然后在main函数中添加调用代码如下:

(5)保存工程并运行,运行结果如图3-23所示。

图3-23

有没有发现上面的SM4加解密函数输入的数据长度要求是16的倍数,那如果不是16的倍数该如何处理呢?这涉及短块加密的问题,短块加密的话题我们前面介绍过了,限于篇幅这里就不再实现了。 +pgqcEHwV8rATQ8pXx3hvlSmWBeQ11awQ5j3gokI21YnmfnvqqaXo65lMmDrsjrj

点击中间区域
呼出菜单
上一章
目录
下一章
×