主要差异如下:
1、 对于待加密解密的数据,各自的填充模式不一样
C#的模式有:ANSIX923、ISO10126、None、PKCS7、Zero,而Java有:NoPadding、PKCS5Padding、SSL3Padding
2、 各自默认的3DES实现,模式和填充方式不一样
C#的默认模式为CBC,默认填充方式为PKCS7; java的默认模式为ECB,默认填充方式为PKCS5Padding
3、 各自的key的size不一样
C#中key的size为16和24均可;java中要求key的size必须为24;对于CBC模式下的向量iv的size两者均要求必须为8
翻看了3DES的原理:
DES主要采用替换和移位的方法,用56位密钥对64位二进制数据块进行加密,每次加密可对64位的输入数据进行16轮编码,
经一系列替换和移位后,输入的64位转换成安全不同的64的输出数据
.
3DES:是在DES的基础上采用三重DES,即用两个56位的密钥K1,K2,发送方用K1加密,K2解密,再使用K1加密.接收方使用K1解密,K2加密,再使用K1解密,
其效果相当于密钥长度加倍.
于是尝试在java中,对key进行补位,即用前8个字节作为byte[24] 中的byte[16]~byte[23];发现与c#中加密的结果相同!于是大胆假设C#中可能是检查key的size为16的时候
自动将前8个字节作为k3进行了补位,而java没有实现这一点(因为java的3DES算法中强制要求key的size必须为24)。这样的情况下,可能就是发送方用k1加密、k2解密、k3再加密;接受方k3解密、k2加密、再k1解密来实现。
最终经过编码验证,确认key大小为24时,java和c#的加密解密结果相一致。
Java中实现时,只要注意对大小不足24的key进行补位,和采用CBC模式,填充模式为PKCS5Padding即可。
2
3 public static byte[] encrypt(String sKey, byte[] bIV, byte[] bPlainText, int nOffset, int nSize)
4 throws Exception {
5 byte[] bKey = buildKey(sKey);
6 byte[] bInput = buildInput(nSize, bPlainText, nOffset);
7 byte[] bResult = encrypt(bIV, bKey, bInput);
8 return bResult;
9 }
10
11 public static byte[] decrypt(String sKey, byte[] bIV, byte[] bCipherText)
12 throws Exception {
13 byte[] bKey = buildKey(sKey);
14 SecretKey securekey = buildSecretKey(bKey);
15 IvParameterSpec iv = new IvParameterSpec(bIV);
16 Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
17 SecureRandom sr = new SecureRandom();
18 cipher.init(Cipher.DECRYPT_MODE, securekey, iv, sr);
19 byte[] bOutput = cipher.doFinal(bCipherText);
20 int nLen = 0;
21 nLen = nLen | (int) bOutput[0];
22 nLen = nLen | ((int) bOutput[1] << 8);
23 if (nLen > bOutput.length - 4) {
24 throw new Exception("非法的密文");
25 }
26 byte[] bResult = new byte[bOutput.length - 4];
27 bResult = Arrays.copyOfRange(bOutput, 2, bOutput.length - 2);
28 return bResult;
29 }
30
31 private static byte[] md5Digest(String sData) throws NoSuchAlgorithmException {
32 MessageDigest md5 = MessageDigest.getInstance("MD5");
33 byte[] bData = sData.getBytes();
34 md5.update(bData, 0, bData.length);
35 byte[] hash = md5.digest();
36 return hash;
37 }
38
39 private static byte[] buildInput(int nSize, byte[] bPlainText, int nOffset) {
40 byte[] bInput = new byte[nSize + 4];
41 for (int i = 0; i < bPlainText.length; i++) {
42 Arrays.fill(bInput, 2 + i, 2 + i + 1, bPlainText[i]);
43 }
44 int usCRC = CCRC16.CalcCrcCheckSum(bPlainText, nOffset, nSize);
45 bInput[0] = (byte) (nSize & 0xFF);
46 bInput[1] = (byte) (nSize >> 8 & 0xFF);
47 bInput[nSize + 2] = (byte) (usCRC & 0xFF);
48 bInput[nSize + 3] = (byte) (usCRC >> 8 & 0xFF);
49 return bInput;
50 }
51
52 private static byte[] buildKey(String sKey) throws NoSuchAlgorithmException {
53 byte[] bTmp = md5Digest(sKey);
54 byte[] bKey = new byte[24];
55 System.arraycopy(bTmp,0,bKey,0,bTmp.length);
56 System.arraycopy(bTmp,0,bKey,16,8);
57 return bKey;
58 }
59
60 private static SecretKey buildSecretKey(byte[] bkey) throws InvalidKeyException,
61 InvalidKeySpecException, NoSuchAlgorithmException {
62 DESedeKeySpec dks = new DESedeKeySpec(bkey);
63 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
64 SecretKey securekey = keyFactory.generateSecret(dks);
65 return securekey;
66 }
67
68 private static byte[] encrypt(byte[] biv, byte[] bkey, byte[] input) throws Exception {
69 SecretKey securekey = buildSecretKey(bkey);
70 IvParameterSpec iv = new IvParameterSpec(biv);
71 Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
72 SecureRandom sr = new SecureRandom();
73 cipher.init(Cipher.ENCRYPT_MODE, securekey, iv, sr);
74 return cipher.doFinal(input);
75 }
76 }
77
78 class CCRC16 {
79 public static int CalcCrcCheckSum(byte[] pBuffer, int nOffset, int nSize) {
80
81 int[] table = {
82 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
83 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
84 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
85 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
86 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
87 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
88 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
89 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
90 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
91 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
92 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
93 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
94 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
95 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
96 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
97 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
98 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
99 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
100 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
101 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
102 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
103 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
104 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
105 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
106 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
107 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
108 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
109 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
110 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
111 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
112 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
113 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,};
114
115 byte[] bytes = pBuffer;
116 int crc = 0x0000;
117 for (byte b : bytes) {
118 crc = (crc >>> 8) ^ table[(crc ^ b) & 0xff];
119 }
120
121 return crc;
122 }
还有一种让Java和.Net兼容的方式,在.Net中指定模式为ECB,填充为PKCS7,然后在Java中采用其默认的模式(DESede/ECB/PKCS5Padding)即可,注意双方约定key的size为24个字节。建议双方对key以base64编码字符串进行告知,因为java和.net中byte字节的范围不相同(前者-128~127,后者0~255),避免不必要的处理。