参考
http://baike.baidu.com/view/350958.htm
http://blog.163.com/zhqh43@126/blog/static/4043302720093276344788/
背景
最近由于公司项目的需要,最近研究了3DES算法运用,我的公司有一个合作的公司,提供webservice接口,是C#写的,用到3DES加密算法,C#代码以下:
public static string Encrypt3DES(string a_strString, string a_strKey) { TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider(); DES.Key = ASCIIEncoding.ASCII.GetBytes(a_strKey); DES.Mode = CipherMode.ECB; ICryptoTransform DESEncrypt = DES.CreateEncryptor(); byte[] Buffer = ASCIIEncoding.ASCII.GetBytes(a_strString); return Convert.ToBase64String(DESEncrypt.TransformFinalBlock(Buffer, 0, Buffer.Length)); }
public static string Decrypt3DES(string a_strString, string a_strKey) { TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider(); DES.Key = ASCIIEncoding.ASCII.GetBytes(a_strKey); DES.Mode = CipherMode.ECB; DES.Padding = System.Security.Cryptography.PaddingMode.PKCS7; ICryptoTransform DESDecrypt = DES.CreateDecryptor(); string result = ""; try { byte[] Buffer = Convert.FromBase64String(a_strString); result = ASCIIEncoding.ASCII.GetString(DESDecrypt.TransformFinalBlock(Buffer, 0, Buffer.Length)); } catch (Exception e) { } return result; }
解法
直接使用公司框架中的3DES算法类,代码如下:
package com.cn.crypt.implement; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.Security; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import org.apache.log4j.Logger; /** * <p> * Title: 3DES加密算法实现 * </p> * * <p> * Description: 3DES加密算法实现 * </p> * * <p> * Copyright: Copyright (c) 2007 * </p> * * <p> * Company: com.cn * </p> * * @author LinPeng * @version 1.0 */ public class Crypto3DESImpl implements Crypto3DSModule { /** * logger 日志 */ public static Logger logger = Logger.getLogger(Crypto3DESImpl.class); /** * 将byte数组转换为表示16进制值的字符串, 如:byte[]{8,18}转换为:0813, 和public static byte[] * hexStr2ByteArr(String strIn) 互为可逆的转换过程 * * @param arrB * 需要转换的byte数组 * @return 转换后的字符串 * @throws Exception * 本方法不处理任何异常,所有异常全部抛出 */ private static String byteArr2HexStr(byte[] arrB) throws Exception { int iLen = arrB.length; // 每个byte用两个字符才能表示,所以字符串的长度是数组长度的两倍 StringBuffer sb = new StringBuffer(iLen * 2); for (int i = 0; i < iLen; i++) { int intTmp = arrB[i]; // 把负数转换为正数 while (intTmp < 0) { intTmp = intTmp + 256; } // 小于0F的数需要在前面补0 if (intTmp < 16) { sb.append("0"); } sb.append(Integer.toString(intTmp, 16)); } return sb.toString(); } /** * 将表示16进制值的字符串转换为byte数组, 和public static String byteArr2HexStr(byte[] arrB) * 互为可逆的转换过程 * * @param strIn * 需要转换的字符串 * @return 转换后的byte数组 * @throws Exception * 本方法不处理任何异常,所有异常全部抛出 * @author <a href="mailto:[email protected]">ZhangJi</a> */ private static byte[] hexStr2ByteArr(String strIn) throws Exception { byte[] arrB = strIn.getBytes(); int iLen = arrB.length; // 两个字符表示一个字节,所以字节数组长度是字符串长度除以2 byte[] arrOut = new byte[iLen / 2]; for (int i = 0; i < iLen; i = i + 2) { String strTmp = new String(arrB, i, 2); arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16); } return arrOut; } // 加密和解密函数的使用用法实例 public static void main(String[] args) throws Exception { Crypto3DESImpl des = new Crypto3DESImpl(); // 设置算法 des.setDesAlgorithm("DES"); // 设置密钥 des.setKeyString("18947f2c467c4763fd2492cf"); String str = des.decrypt("8394bef13776eae45547cb484e075963f9a7e9542301f87a82ef9a4dcaf4b0c5e1bcc66c34ecabf1b8d8012a7357c85271ae3ca2a19887cb6228677a84acd22ae3aa4a230a30f91557fa8677fe6c8edb81cf8e93f3128cb841a3b863f0a790bfc9bbc8c6c5979df32ca3f7fb1ffff1ad0e30bf8bf14c1a778808d82064de6b85d46eb3c1c0b8465e124eb113e5d56e519aa7e92819993ac9"); logger.debug(str); } /** * 3desAlgorithm 3des加密算法,可用 DES,DESede,Blowfish */ public String desAlgorithm; /** * keyString 加密密钥 */ String keyString; public Crypto3DESImpl() { Security.addProvider(new com.sun.crypto.provider.SunJCE()); } /** * decrypt 3DES单参数解密函数 * * @param src * byte[] * @return byte[] * @throws Exception */ private byte[] decrypt(byte[] src) throws Exception { byte[] keybyte = keyString.getBytes(); // 生成密钥 Key deskey = getKey(keybyte); Cipher decryptCipher = Cipher.getInstance(desAlgorithm); decryptCipher.init(Cipher.DECRYPT_MODE, deskey); return decryptCipher.doFinal(src); } /** * decrypt 3DES单参数解密函数 * * @param src * String * @return String * @throws Exception */ public String decrypt(String src) throws Exception { byte[] srcBytes = Crypto3DESImpl.hexStr2ByteArr(src); return new String(this.decrypt(srcBytes)); } /** * encrypt 3DES单参数加密函数 * * @param src * byte[] 输入字符数组 * @return byte[] 返回字符数组 * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws BadPaddingException * @throws IllegalBlockSizeException * @throws Exception */ public byte[] encrypt(byte[] src) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, Exception { byte[] keybyte = keyString.getBytes(); // 生成密钥 Key deskey = getKey(keybyte); // 加密 Cipher c1 = Cipher.getInstance(desAlgorithm); c1.init(Cipher.ENCRYPT_MODE, deskey); return c1.doFinal(src); } /** * encrypt 3DES单参数加密函数 * * @param src * String * @return String * @throws Exception */ public String encrypt(String src) throws Exception { byte[] srcBytes = src.getBytes(); return Crypto3DESImpl.byteArr2HexStr(this.encrypt(srcBytes)); } public String getDesAlgorithm() { return desAlgorithm; } /** * 从指定字符串生成密钥,密钥所需的字节数组长度为8位 不足8位时后面补0,超出8位只取前8位 * * @param arrBTmp * 构成该字符串的字节数组 * @return 生成的密钥 * @throws Exception */ private Key getKey(byte[] arrBTmp) throws Exception { // 创建一个空的8位字节数组(默认值为0) byte[] arrB = new byte[8]; // 将原始字节数组转换为8位 for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) { arrB[i] = arrBTmp[i]; } // 生成密钥 Key key = new SecretKeySpec(arrB, desAlgorithm); return key; } public String getKeyString() { return keyString; } public void setDesAlgorithm(String desAlgorithm) { this.desAlgorithm = desAlgorithm; } public void setKeyString(String keyString) { this.keyString = keyString; } }
测试之后发现,C#加密与解密完全与上面的java代码3DES结果不一样,不知道怎么回事,难道是我们框架代码有问题,那我就开始在网上搜索3DES相关例子,发现3DES还有两个重要参数:模式与填充方式,又仔细看了看C#代码中提到了ECB与PKCS7,正是3DES重要参数,但我还是不会写,又在网上找,代码如下:
package com.cn.crypt.implement; import java.net.URLEncoder; import java.security.Security; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.jce.provider.BouncyCastleProvider; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import com.cn.crypt.IDESecbpkcs7; /** * @author 刘宗安 * @version 1.0 功能 :釆用3DES标准以模式为ECB、填充方式为PKCS7加密数据 */ public class DESecbpkcs7Impl implements IDESecbpkcs7 { private Cipher cipher = null; // base64编码 private BASE64Encoder base64Encode = new BASE64Encoder(); private BASE64Decoder base64Decode = new BASE64Decoder(); // 密钥 private String key = ""; // 过滤换行 private boolean filter = true; public String getKey() { return key; } public boolean getFilter() { return filter; } /** * 设置密钥 * @param key */ public void setKey(String key) { this.key = key; } public void setFilter(boolean filter) { this.filter = filter; } private final Cipher initCipher(int mode) { try { // 添加新安全算法:PKCS7 Security.addProvider(new BouncyCastleProvider()); String algorithm = "DESede/ECB/PKCS7Padding"; SecretKey desKey = new SecretKeySpec((new BASE64Decoder()).decodeBuffer(key), algorithm); //SecretKey desKey = new SecretKeySpec(key.getBytes("ASCII"), algorithm); Cipher tcipher = Cipher.getInstance(algorithm); tcipher.init(mode, desKey); return tcipher; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 加密以charset编码做为密文 * * @param src * 明文 * @param charset * 编码,例:UTF8、BASE64 * @return */ public String encrypt(String src, String charset) { try { return URLEncoder.encode(encrypt(src), charset); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密 * @param src 二进制数组 * @return * @throws Exception */ private byte[] decrypt(byte[] src) throws Exception { cipher = initCipher(Cipher.DECRYPT_MODE); return cipher.doFinal(src); } /** * 解密 * @param src 密文 * @return * @throws Exception */ public String decrypt(String src) throws Exception { byte[] bt=base64Decode.decodeBuffer(src); byte[] sbt=decrypt(bt); return new String(sbt,"ASCII"); } /** * 加密以base64做为密文 * * @param src * 明文 * @return 密文 */ public String encrypt(String src) { cipher = initCipher(Cipher.ENCRYPT_MODE); byte[] dd = encrypt(src.getBytes()); String str = base64Encode.encode(dd); str = str.replaceAll("\r", ""); str = str.replaceAll("\n", ""); return str; } /** * * @param src * @return */ public byte[] encrypt(byte[] src) { try { return cipher.doFinal(src); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception { DESecbpkcs7Impl cWebService3DES = new DESecbpkcs7Impl(); Security.addProvider(new com.sun.crypto.provider.SunJCE()); cWebService3DES.key = "18947f2c467c4763fd2492cf".toUpperCase(); System.out.println("18947f2c467c4763fd2492cf".toUpperCase()); String s = cWebService3DES .encrypt("<?xml version=\"1.0\" encoding=\"utf-8\" ?><SXData><UserID>bjtele-union</UserID><EnCode>9289a37b0f5cb18abb5aa4d33e85afe9</EnCode></SXData>"); s = s.replaceAll("\r", ""); s = s.replaceAll("\n", ""); System.out.println(s); System.out.println(cWebService3DES.decrypt(s)); } }
测试之后,发现与C#还是不一样,这下我可晕,不知道从哪里修改好?哎,真不知道从何下手了,又继续找原因,后来发现上面C#代码中存在这一段代码:
DES.Key = ASCIIEncoding.ASCII.GetBytes(a_strKey);
呵呵,将以前的BASE64编码代码
SecretKey desKey = new SecretKeySpec((new BASE64Decoder()).decodeBuffer(key), algorithm);
修改java代码KEY为ASCII:
SecretKey desKey = new SecretKeySpec(key.getBytes("ASCII"), algorithm);
这一下终于调试成功了,最后还要提醒一下将加密后的二进制数组以base64编码进行显示,代码如下:
String str = base64Encode.encode(dd);
OK