关于3DES算法C版本与JAVA版本的兼容问题


http://argv.info/2011/01/06/c-on-the-3des-algorithm-version-version-and-java-compatibility/

今天在一个项目中遇到报文中密码字段3DES加密的情况,对方提供了JAVA版的例子,我这里下载了一个C版本的3DES实现源码包,尝试后发现加密后结果不同。

在网上查了下,发现有很多和这个情况一样,在《用java实现3des加密》一文中提到:

对于其他语言开发的3des,一定要采用相同的mode和padding才能保证通信。

重点就在这里,这里说的mode和padding是什么意思呢?(注:以下内容大段抄袭《DES 算法的 C++ 与 JAVA 互相加解密》-。-)

首先说mode。mode在这里指加密模式。常见的加密模式有ECB/CBC/CFB/OFB四种。加密算法是按块进行加密的,例如DES是64bit(8 bytes)一个块进行加密,每次输入8个字节的明文进行加密,输出8个字节密文,如果是明文一共16个字节长,则分成两组加密。例如明文是12345678 12345678(空格为了查看方便,实际不包含,下同),那么加密的结果类似C4132737962C519C  C4132737962C519C,如果是你,看到密文什么感想?没错,这个应该是一组数据重复两遍吧?这中加密模式就是ECB,分组之间没有联系,只要组和组明文相同,那么他们的密文也是相同的。为了解决这个问题,出现了其他的加密模式,分别为CBC 加密模式(密码分组连接),CFB加密模式(密码反馈模式),OFB加密模式(输出反馈模式)。CBC 是要求给一个初始化的向量,然后将每个输出与该向量作运算,并将运算的结果作为下一个加密块的初始化向量,CFB 和 OFB 则不需要提供初始化向量,直接将密码或者输出作为初始化向量进行运算;这样就避免了明文的规律出现在密文中;当然缺点是解密时需要保证密文的正确性,如果网络传输时发生了一部分错误,则后面的解密结果就可能是错误的(ECB模式仅影响传输错误的那个块)。

接下来是padding。padding在这里指填充方式,假如明文是10位,按照8bytes分组,正好1组多2个byte,这2个byte怎么加密?这时候必须对明文进行填充。填充方式很多,具体可以参考《Using Padding in Encryption》一文。常用的PKCS#7,该填充方法是将每一个补充的字节内容填充为填充的字节个数;例如明文长度是 100 , 分组的大小是32个字节,那么需要分为四组,补充28个字节,那么补充的字节全部补充为’\0×28′。还有一种PKCS#5,和PKCS#7的区别就是,分组的大小为8个字节。另外还有一个规定,就是如果明文刚刚好进行分组,那么需要补充一个独立的分组出来。例如 DES明文:12345678,为 8 个字节,则必须补充8个0×08至16个字节,然后进行加密;解密后的字符串为12345678\x08\x08\x08\x08\x08\x08\x08\x08,需要将后面的0×08去掉,就能得到原始明文。

知道了这些,再去看下JAVA版本的3DES加解密。
private static final String Algorithm = "DESede"; //定义加密算法,可用DES,DESede,Blowfish 在这里,DESede算法即为传说中的3DES加密,其mode为ECB,padding为PKCS#5。按照上面所描述的,我封装了两个函数。

首先是包含的头文件,以及预定义内容:



#include "d3des.h"        
#define BLOCK_BYTES 8        
#define SetKey(a,b) des3key((a),(b))        
#define DoCrypt(a,b) Ddes((a),(b))



其中d3des.h为网上下载的3des的C版本实现。网上很好找。

然后是加密函数:将from中长度为len个字节的明文,经过密钥key的DESede加密,获得密文存放在to中,函数返回值为密文长度。



unsigned         int        
 DESede_Encrypt        (        char         *to,         char         *from,         unsigned         int len,         char         *key        )        
{        
             int  i        ;        
             unsigned         int padding_bytes        ;        
             char         *data        ;        
             unsigned         char         *p,         *q        ;        

     data         =         (        char         *        )        malloc        (        sizeof        (        char        )        *len        +BLOCK_BYTES        )        ;        
             memcpy        (data, from, len        )        ;        

             /* PKCS5 padding */
     padding_bytes         = BLOCK_BYTES         - len         % BLOCK_BYTES        ;        
             for         ( i        =        0        ; i        <padding_bytes        ;         ++i         )         {        
         data        [len        +i        ]         =         (        unsigned         char        )padding_bytes        ;        
             }        
     len         +        = padding_bytes        ;        

             /* encrypt */
     SetKey        (key, EN0        )        ;        
             for         ( p        =data, q        =to        ; p        <        (        unsigned         char         *        )data        +len        ; p        +        =BLOCK_BYTES, q        +        =BLOCK_BYTES         )         {        
         DoCrypt        (p, q        )        ;        
             }        

             free        (data        )        ;        
             return len        ;        
}



解密函数:将from中长度为len个字节的密文,经过密钥key的DESede解密,获得明文存放在to中,函数返回值为明文长度。



unsigned         int        
 DESede_Decrypt        (        char         *to,         char         *from,         unsigned         int len,         char         *key        )        
{        
             unsigned         char         *p,         *q        ;        
     SetKey        (key, DE1        )        ;        
             for         ( p        =from, q        =to        ; p        <        (        unsigned         char         *        )from        +len        ; p        +        =BLOCK_BYTES, q        +        =BLOCK_BYTES         )         {        
         DoCrypt        (p, q        )        ;        
             }        
             return len        -        (        unsigned         int        )to        [len        -        1        ]        ;


}



以上两个函数中的key(24位)以及其他输入参数都需要函数调用者负责校验。

参考资料: [1] 用java实现3des加密 [2] DES 算法的 C++ 与 JAVA 互相加解密 [3] Using Padding in Encryption [4] Block cipher modes of operation

分类: Algorithm, C/CPP