本帖最后由 某公司安全产品研发 于 2016-3-30 09:44 编辑
作者:某公司移动应用部
DES加解密详解
一、DES算法描述 DES算法总的说来可以两部分组成:
1、对密钥的处理。这一部分是把我们用的64位密钥(实际用的56位,去掉了8个奇偶校验位)分散成16个48位的子密钥。
2、对数据的加密。通过第一步生成的子密钥来加密我们所要加密的数据,最终生成密文。
下面就通过这两部分分别介绍DES算法的实现原理。
1.密钥分散——子密钥的生成 64比特的密钥生成16个48比特的子密钥。其生成过程见图:
64比特的密钥K,经过PC-1后,生成56比特的串。其下标如表所示:
该比特串分为长度相等的比特串C0和D0(分别为28比特)。然后C0和D0分别循环左移1位,得到C1和D1。C1和D1合并起来生成C1D1。C1D1经过PC-2变换后即生成48比特的K1。K1的下标列表为:
C1、D1分别循环左移LS2位,再合并,经过PC-2,生成子密钥K2……依次类推直至生成子密钥K16。 注意:Lsi (I =1,2,….16)的数值是不同的。具体见下表:
注:PC-1 和 PC-2是密钥的指定为置换。
至此,我们已成功的生成了16个48位的子密钥。
2.加密流程图 DES算法处理的数据对象是一组64比特的明文串。设该明文串为m=m1m2…m64(mi=0或1)。明文串经过64比特的密钥K来加密,最后生成长度为64比特的密文E。其加密过程图示如下:
3. DES算法加密过程 对DES算法加密过程图示的说明如下:待加密的64比特明文串m,经过IP置换后,得到的比特串的下标列表如下:
该比特串被分为32位的L0和32位的R0两部分。R0子密钥K1经过变换f(R0,K1)(f变换算法见下)输出32位的比特串f1,f1与L0做异或运算。
f1与L0做异或运算后的结果赋给R1,R0则原封不动的赋给L1。L1与R0又做与以上完全相同的运算,生成L2,R2…… 一共经过16次运算。最后生成R16和L16。其中R16为L15与f(R15,K16)做不进位二进制加法运算的结果,L16是R15的直接赋值。
R16与L16合并成64位的比特串。值得注意的是R16一定要排在L16前面。R16与L16合并后成的比特串,经过置换IP-1后所得比特串的下标列表如下:
经过置换IP-1后生成的比特串就是密文e.。
f 算法 变换f(Ri-1,Ki)的功能是将32比特的输入再转化为32比特的输出。其过程如图所示:
首先、输入Ri-1(32比特)经过变换E后,膨胀为48比特。膨胀后的比特串的下标列表如下:
其次、膨胀后的E和Ki异或的结果分为8组,每组6比特。各组经过各自的S盒后,变为4比特,
S盒的算法为:输入b1,b2,b3,b4,b5,b6,计算x=b1*2+b6,y=b5+b4*2+b3*4+b2*8,再从Si表(见下表)中查出x 行,y 列的值Sxy。将Sxy化为二进制,即得Si盒的输出。
最后、合并8组S盒输出成为32比特。该32比特经过P变换后,其下标列表如下:
经过P变换后输出的比特串才是32比特的f (Ri-1,Ki)。
以上介绍了DES算法的加密过程。DES算法的解密过程是一样的,区别仅仅在于第一次迭代时用子密钥K16,第二次K15、......,最后一次用K1,算法本身并没有任何变化。 二、目前使用的DES算法
对于服务端apache,里面描述的DES加解密,只是最基本、最原始的加解密,而现在很多地方使用的DES都会有一些扩展。
接来说下我们目前使用的DES的加解密的使用。
函数des3_set_3keys设置key,如果是3DES,则需要设置3个key,这里说的key其实就是8字节的数组类型的密钥;
而函数des3_encrypt是处理的加密,void des3_encrypt( des3_context *ctx, uint8 input[8], uint8output[8] ),其中的input和output分别表示需要加密的和加密后的数据。这里提供的是8个字节的数据,如果要加密的数据比8个字节要长,则需要循环使用这个加密函数;而输出output,每次调用des3_encrypt,输出都是8位。比如说,需要加密的数据是个10字节长的数,那么加完密之后则是16位。
三、DES算法的两种模式
上面描述的只是是最基本的DES加密,而通常外界使用的DES很多都有模式以及填充方式的设置,如果设置不一样,将会导致一些接口信息不一致,加解密的数据就对不上。
比较常用的模式有:cbc和ecb。
这里主要介绍DES算法的数据补位问题、DES算法的两种模式ECB和CBC问题,以及更加安全的算法3DES。
1、数据补位
DES数据加解密就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节(通常补00或者FF,根据实际要求不同)进行计算,之后按照顺序将计算所得的数据连在一起即可。
很多地方默认的补位方式是以PKCS7补位的,如果C#默认的就是PKCS7补位:补位补到8位的整数倍,差几位补几。
这里有个问题就是为什么要进行数据补位?主要原因是DES算法加解密时要求数据必须为8个字节。
2、ECB模式
DES ECB(电子密本方式)其实非常简单,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节进行计算,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。
3、CBC模式 DES CBC(密文分组链接方式)有点麻烦,它的实现机制使加密的各段数据之间有了联系。其实现的机理如下:
加密步骤如下:
1)首先将数据按照8个字节一组进行分组得到D1D2......Dn(若数据不是8的整数倍,用指定的PADDING数据补位)
2)第一组数据D1与初始化向量I异或后的结果进行DES加密得到第一组密文C1(初始化向量I为全零)
3)第二组数据D2与第一组的加密结果C1异或以后的结果进行DES加密,得到第二组密文C2
4)之后的数据以此类推,得到Cn
5)按顺序连为C1C2C3......Cn即为加密结果。
解密是加密的逆过程,步骤如下:
1)首先将数据按照8个字节一组进行分组得到C1C2C3......Cn
2)将第一组数据进行解密后与初始化向量I进行异或得到第一组明文D1(注意:一定是先解密再异或)
3)将第二组数据C2进行解密后与第一组密文数据进行异或得到第二组数据D2
4)之后依此类推,得到Dn
5)按顺序连为D1D2D3......Dn即为解密结果。
这里注意一点,解密的结果并不一定是我们原来的加密数据,可能还含有你补得位,一定要把补位去掉才是你的原来的数据。
4、3DES 算法
3DES算法顾名思义就是3次DES算法,其算法原理如下:
设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,P代表明文,C代表密表,这样, 3DES加密过程为:C=Ek3(Dk2(Ek1(P)))
3DES解密过程为:P=Dk1((EK2(Dk3(C)))
这里可以K1=K3,但不能K1=K2=K3(如果相等的话就成了DES算法了)
3DES with 2 diffrent keys(K1=K3),可以是3DES-CBC,也可以是3DES-ECB,3DES-CBC整个算法的流程和DES-CBC一样,但是在原来的加密或者解密处增加了异或运算的步骤,使用的密钥是16字节长度的密钥,将密钥分成左8字节和右8字节的两部分,即k1=左8字节,k2=右8字节,然后进行加密运算和解密运算。
3DES with 3 different keys,和3DES-CBC的流程完全一样,只是使用的密钥是24字节的,但在每个加密解密加密时候用的密钥不一样,将密钥分为3段8字节的密钥分别为密钥1、密钥2、密钥3,在3DES加密时对加密解密加密依次使用密钥1、密钥2、密钥3,在3DES解密时对解密加密解密依次使用密钥3、密钥2、密钥1。
四、DES两种模式的算法实现下面提供几个代码例子,来描述DES算法两种模式的实现,均以PKCS7来进行补位(代码仅做参考)。
1.ECB模式以PKCS7方式填充的DES加密int des_ecb_pkcs7_encrypt(uchar* from, int nLength, uchar * to, uchar key[])
file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image023.png{ int nSize = nLength % 8 ?(nLength + 7) / 8 * 8 : nLength + 8; if(to == NULL) file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image024.png{ //计算长度 return nSize; } else file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image025.png{ deskey(key,EN0); uchar endBuf[8]; int i=0; for(; i < nSize; i+=8) file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image026.png{ uchar* ps = NULL,* pd = NULL; if(nLength - i >= 8) file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image027.png{ ps = from + i; pd = to + i; } else file://localhost/Users/ZJ/Library/Caches/TemporaryItems/msoclip/0/clip_image028.png{ memset(&endBuf, i + 8 - nLength, sizeof(endBuf)); memcpy(&endBuf,from + i,nLength - i); ps = endBuf; pd = to + i; } des(ps,pd);
} return i; }
}
2.ECB模式以PKCS7方式填充的DES解密int des_ecb_pkcs7_decrypt(uchar* from,int nLength, uchar * to, uchar key[]) { if(nLength % 8) return 0; //数据不正确 deskey(key,DE1); int i = 0; for(; i < nLength; i+=8) { if(nLength - i > 8) { des(from + i,to + i); } else { uchar endBuf[8]; des(from + i,endBuf); //去除数据尾 uchar chEnd = endBuf[7]; if(chEnd > 0 && chEnd< 9) { //有可能是填充字符,去除掉 for(int j = 7; j >= 8 -chEnd; --j) { if(endBuf[j] != chEnd) return 0; } memcpy(to + i, endBuf, 8 -chEnd); return i + 8 - chEnd; } else { return 0; } } } return 0; } 3.CBC模式以PKCS7方式填充的DES加密 其中iv就是上面提到的初始化向量。 intdes_cbc_pkcs7_encrypt(uchar* from, int nLength, uchar * to, uchar key[],uchar iv[]) { //uchar buffer[8]; int nSize = nLength % 8 ?(nLength + 7) / 8* 8 : nLength + 8; if(to == NULL) { //计算长度 return nSize; } else { deskey(key,EN0); uchar preEnc[8]; memcpy(preEnc,iv,8); //加密块 int i=0; for(; i < nSize; i+=8) { uchar* ps = from + i; uchar* pd = to + i; if(nSize - i > 8) { //XOR for(int j = 0; j < 8; ++j) { preEnc[j] ^= *(ps + j); } } else { //XOR for(int j = 0; j < nLength -i; ++j) { preEnc[j] ^= *(ps + j); } for(int j = nLength - i; j <8; ++j) { preEnc[j] ^= nSize - nLength; } } des(preEnc,pd); //保存前一个输出 memcpy(preEnc, pd,8); } return i; } } 4.CBC模式以PKCS7方式填充的DES解密 intdes_cbc_pkcs7_decrypt(uchar* from, int nLength, uchar * to, uchar key[], uchar iv[]) { if(nLength % 8) return 0; //数据不正确 //uchar* toBackup = to; //to = (uchar*)malloc(nLength); //申请内存 //XOR uchar preEnc[8],buffer[8]; memcpy(preEnc,iv,8); deskey(key,DE1); int i = 0; for(;i < nLength; i+=8) { uchar* ps = from + i; uchar* pd = to + i; des(ps,buffer); //XOR for(int j = 0; j < 8; ++j) { buffer[j] ^= preEnc[j]; } if(nLength - i > 8) { //保存前一个输出 memcpy(preEnc, ps,8); memcpy(pd,buffer,sizeof(buffer)); } else { //去除数据尾 uchar chEnd = buffer[sizeof(buffer)- 1]; if(chEnd > 0 && chEnd <9) { //有可能是填充字符,去除掉 for(int j = sizeof(buffer) - 1;j >= (int)(sizeof(buffer) - chEnd); --j) { if(buffer[j] != chEnd) return 0; } int nSize =nLength - chEnd; memcpy(pd, buffer,sizeof(buffer) - chEnd); return nLength - chEnd; } else { //数据格式不正确 return 0; } } } return 0; } |