C端同事给的密文(十六进制)是32位,但是我这边生成的是48位
刚开始工具类中使用的
Cipher cipher=Cipher.getInstance("DESede");
后来把这个改为
Cipher cipher=Cipher.getInstance("DESede/ECB/NoPadding");
生成的32位 与C端相同
Java 解密、工具类 见下文
Java 官方附录: http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#AlgorithmParameterGenerator
Cipher cipher=Cipher.getInstance("DESede/CBC/PKCS5Padding");
以前写的代码,给的参数都是DES或DESede。实际上DESede是简写,它与DESede/ECB/PKCS5Padding等价。这个参数分为三段。
- 第一段是加密算法的名称,如DESede实际上是3-DES。这一段还可以放其它的对称加密算法,如Blowfish等。
- 第二段是分组加密的模式,除了CBC和ECB之外,还可以是NONE/CFB/QFB等。最常用的就是CBC和ECB了。DES采用分组加密的方式,将明文按8字节(64位)分组分别加密。如果每个组独立处理,则是ECB。CBC的处理方式是先用初始向量IV对第一组加密,再用第一组的密文作为密钥对第二组加密,然后依次完成整个加密操作。如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,但CBC则不会。
- 第三段是指最后一个分组的填充方式。大部分情况下,明文并非刚好64位的倍数。对于最后一个分组,如果长度小于64位,则需要用数据填充至64位。PKCS5Padding是常用的填充方式,如果没有指定,默认的方式就是它。
补充一点,虽然DES的有效密钥长度是56位,但要求密钥长度是64位(8字节)。3DES则要求24字节。
出处: http://www.cnblogs.com/qkhh/p/4683626.html
MODE指的是加密模式。加密算法是按块进行加密的,DES是64bit(8 bytes)一个块进行加密,每次输入8个字节的明文,输出8个字节密文,如果是明文一共16个字节长,则分成两组依次进行加密。
(1) 例如明文是 1234567812345678,那么加密的结果类似C21431262C779CC21431262C779C,看出了什么没,是的,重复了两遍,这钟MODE就是ECB。分组之间没有联系,组和组明文相同,那么密文也是相同的,容易被发现规律。
(2)为了解决这个问题,出现了新的加密模式,分组连接模式(CBC),密码反馈模式(CFB),输出反馈模式(OFB)。
CBC 是需要给一个初始化的向量,然后将每个输出与该向量作运算,并将运算的结果作为下一个加密块的初始化向量,CFB 和 OFB 则不需要提供初始化向量,直接将密码或者输出作为初始化向量进行计算;避免了明文的规律性出现反应在密文中。当然带来的问题是,解密的时候必须接收完整才能开始解密。如果其中部分信息网络接收出错,会导致整个解密失败,而ECB仅影响网络传输出错的那个块的解密。
其次、解释上段落第3点钟提到的PADDING。padding在这里指填充方式,例如明文是10位,按照8bytes分组,正好1组多2个byte,这2个byte怎么加密?这时候必须对明文进行Padding操作。padding的方式很多,在《跨语言的加解密兼容问题讨论》 一文中有比较想起的对比。
(1)一种PKCS#7,该填充方法是将每一个补充的字节内容填充为填充的字节个数;在这里我们需要填充的6个'0x6';
(2)另一种PKCS#5,和PKCS#7的区别就 是,分组的大小为8个字节。
(3)另外,就是如果明文刚刚好进行分组,那么需要补充一个独立的分组。如 DES明文:12345678,为 8 个字节,则必须补充8个0×08至16个字节,然后进行加密;解密后的字符串为12345678\x08\x08\x08\x08\x08\x08\x08\x08,需要去掉多余的, 0×08的到原文。
出处: http://www.mythroad.net/2012/11/01/3des%E7%AE%97%E6%B3%95java%E4%B8%8Ec%E7%9A%84%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0/
DESUtil
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
public class DESUtil {
private static final String Algorithm = "DESede"; //定义 加密算法,可用 DES,DESede,Blowfish
private static final String AlgorithmP = "DESede/ECB/NoPadding";
public static String Decrypt3DES(String value, String key) throws Exception {
// byte[] b = decryptMode(GetKeyBytes(key), Base64.decode(value));
byte[] b = decryptMode(GetKeyBytes(key), HexUtil.decodeHex(value.toCharArray()));
return new String(b);
}
public static String Encrypt3DES(String value, String key) throws Exception {
// String str = byte2Base64(encryptMode(GetKeyBytes(key), value.getBytes()));
String str = HexUtil.encodeHexStr(encryptMode(GetKeyBytes(key), value.getBytes()));
return str;
}
//计算24位长的密码byte值,首先对原始密钥做MD5算hash值,再用前8位数据对应补全后8位
public static byte[] GetKeyBytes(String strKey) throws Exception {
if (null == strKey || strKey.length() < 1)
throw new Exception("key is null or empty!");
byte[] bkey = strKey.getBytes();
int start = bkey.length;
byte[] bkey24 = new byte[24];
for (int i = 0; i < start; i++) {
bkey24[i] = bkey[i];
}
for (int i = start; i < 24; i++) {//为了与.net16位key兼容
bkey24[i] = bkey[i - start];
}
return bkey24;
}
//keybyte为加密密钥,长度为24字节
//src为被加密的数据缓冲区(源)
public static byte[] encryptMode(byte[] keybyte, byte[] src) {
try {
//生成密钥
SecretKey deskey = new SecretKeySpec(keybyte, Algorithm); //加密
Cipher c1 = Cipher.getInstance(AlgorithmP);
c1.init(Cipher.ENCRYPT_MODE, deskey);
return c1.doFinal(src);
} catch (java.security.NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (javax.crypto.NoSuchPaddingException e2) {
e2.printStackTrace();
} catch (java.lang.Exception e3) {
e3.printStackTrace();
}
return null;
}
//keybyte为加密密钥,长度为24字节
//src为加密后的缓冲区
public static byte[] decryptMode(byte[] keybyte, byte[] src) {
try { //生成密钥
SecretKey deskey = new SecretKeySpec(keybyte, Algorithm);
//解密
Cipher c1 = Cipher.getInstance(AlgorithmP);
c1.init(Cipher.DECRYPT_MODE, deskey);
return c1.doFinal(src);
} catch (java.security.NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (javax.crypto.NoSuchPaddingException e2) {
e2.printStackTrace();
} catch (java.lang.Exception e3) {
e3.printStackTrace();
}
return null;
}
//转换成base64编码
public static String byte2Base64(byte[] b) {
return Base64.encode(b);
}
//转换成十六进制字符串
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1)
hs = hs + "0" + stmp;
else
hs = hs + stmp;
}
return hs.toUpperCase();
}
/**
* 去掉java加密后会在后面自动填充的8位
* @param src
* @return
*/
public static byte[] withoutAutofill(byte[] src){
byte[] newbyte = new byte[src.length-8];
for(int i=0 ; i<newbyte.length;i++){
newbyte[i] = src[i];
}
return newbyte;
}
public static void main(String[] args) throws Exception {
String x = Encrypt3DES("12341234","000000000222225678");
System.out.println(x);
String y = Decrypt3DES(x, "000000000222225678");
System.out.println(y);
}
}
HexUtil
public class HexUtil {
/**
* 用于建立十六进制字符的输出的小写字符数组
*/
private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* 用于建立十六进制字符的输出的大写字符数组
*/
private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/**
* 将字节数组转换为十六进制字符数组
*
* @param data
* byte[]
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data) {
return encodeHex(data, true);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data
* byte[]
* @param toLowerCase
* <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data
* byte[]
* @param toDigits
* 用于控制输出的char[]
* @return 十六进制char[]
*/
protected static char[] encodeHex(byte[] data, char[] toDigits) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data
* byte[]
* @return 十六进制String
*/
public static String encodeHexStr(byte[] data) {
return encodeHexStr(data, false);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data
* byte[]
* @param toLowerCase
* <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
* @return 十六进制String
*/
public static String encodeHexStr(byte[] data, boolean toLowerCase) {
return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data
* byte[]
* @param toDigits
* 用于控制输出的char[]
* @return 十六进制String
*/
protected static String encodeHexStr(byte[] data, char[] toDigits) {
return new String(encodeHex(data, toDigits));
}
/**
* 将十六进制字符数组转换为字节数组
*
* @param data
* 十六进制char[]
* @return byte[]
* @throws RuntimeException
* 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常
*/
public static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new RuntimeException("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* 将十六进制字符转换成一个整数
*
* @param ch
* 十六进制char
* @param index
* 十六进制字符在字符数组中的位置
* @return 一个整数
* @throws RuntimeException
* 当ch不是一个合法的十六进制字符时,抛出运行时异常
*/
protected static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new RuntimeException("Illegal hexadecimal character " + ch
+ " at index " + index);
}
return digit;
}
public static void main(String[] args) {
String srcStr = "待转换字符串";
String encodeStr = encodeHexStr(srcStr.getBytes());
String decodeStr = new String(decodeHex(encodeStr.toCharArray()));
System.out.println("转换前:" + srcStr);
System.out.println("转换后:" + encodeStr);
System.out.println("还原后:" + decodeStr);
}
}