今天在一个项目中遇到报文中密码字段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。按照上面所描述的,我封装了两个函数。
首先是包含的头文件,以及预定义内容:
1
2 3 4 |
#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中,函数返回值为密文长度。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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中,函数返回值为明文长度。
1
2 3 4 5 6 7 8 9 10 |
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