1、md5或者sha256摘要计算,该加密算法是单向加密,即加密的数据不能再通过解密还原。一般是用于验证数据的一致性,防止节后到的数据包被篡改。相关类包含在java.security.MessageDigest包中。
2、DES,3DES,AES等加密,该加密算法是可逆的,解密方可以通过与加密方约定的密钥匙进行解密。相关类包含在javax.crypto.*包中。
3、base64编码,是用于传输8bit字节代码最常用的编码方式。相关类在sun.misc.BASE64Decoder 和sun.misc.BASE64Encoder 中。
4、URLEncoder编码,是一种字符编码,保证被传送的参数由遵循规范的文本组成。相关类在java.net.URLEncoder包中。
下面主要记录下在工作中使用的sha256验证一致性和3DES加密遇到的问题和解决办法,并非完整的关于java加密算法原理性的文章。
3DES加解规则:使用DESede/CBC/Nopadding加密方式,使用初始化密钥和向量对明文data进行加密,获取加密后的字节dataByteArray,再对dataByteArray进行BASE64Encoder编程成字符串entryData。
3DES解解规则:先用BASE64Decoder对字符串entryData进行解码dataByteArray,再用同样的加密方式+密钥+向量进行解密获取data
/**
* 3DES加密工具类
*/
public class TripleDESUtil {
// 算法名称/加密模式/填充方式
// 算法名称:DES/DESede/AES
// 加密模式:DES共有四种工作模式-->>ECB:电子密码本模式、CBC:加密分组链接模式、CFB:加密反馈模式、OFB:输出反馈模式
// 填充方式:Nopadding/PKCS5Padding/ISO10126Padding 等,填充方式简单可理解为计算中不满8字节自动给补上/u0000
// java不支持PKCS7Padding,接口对接中PHP可以支持7,后来让对方改了
// 一般工作中用的是DES/CBC/PKCS5Padding
private static String TRI_DES = "DESede/CBC/Nopadding";
// 加密密钥,密钥,不为空,为安全起见,要求长度是24位
private static byte[] key = new byte[24];
// 初始化向量,好像不是必要的,CBC模式必须要改值
private static byte[] keyIv = new byte[8];
/**
* 初始化加密密钥
*
* @param keyStr
* 必须为48位的十六进制数,转换成24为的字节数组
* @throws UnsupportedEncodingException
*/
public static void init(String keyStr, String keyIvStr) {
if (StringUtils.isEmpty(keyStr) || keyStr.length() != 24) {
throw new IllegalArgumentException("初始化TripleDES加密key失败,密钥必须为24位的数据:" + keyStr);
}
if (StringUtils.isEmpty(keyIvStr) || keyIvStr.length() != 8) {
throw new IllegalArgumentException("初始化TripleDES加密key失败, 初始化向量必须为8位的数据:" + keyIvStr);
}
byte[] keyTemp = keyStr.getBytes();
if (null == keyTemp || keyTemp.length != 24) {
throw new IllegalArgumentException("初始化TripleDES加密key失败,密钥转换成24位的字节数组失败:" + keyStr);
}
TripleDESUtil.key = keyTemp;
byte[] keyIvTemp = keyIvStr.getBytes();
if (null == keyIvTemp || keyIvTemp.length != 8) {
throw new IllegalArgumentException("初始化TripleDES加密key失败,初始化向量转换成8位的字节数组失败:" + keyIvStr);
}
TripleDESUtil.keyIv = keyIvTemp;
}
/**
* 初始化加密的字符串,计算encryptData的UTF-8字节数据
*
* @param data
* 加密字符串
* @return null:如encryptData为空
*/
private static byte[] initData(byte[] data) {
if (null == data || data.length == 0) {
return null;
}
int descLen = data.length;
if (descLen % 8 != 0) {
descLen = descLen - descLen % 8 + 8;
}
byte[] returnData = new byte[descLen];
for (int i = 0; i < descLen; i++) {
returnData[i] = 0x00;
}
System.arraycopy(data, 0, returnData, 0, data.length);
return returnData;
}
/**
* 3des加密
*
* @param data
* 明文数据,不能为空
* @return 密文数据
*/
private static byte[] union3DesEncrypt(byte[] data) {
try {
byte[] needData = initData(data);
if (null == needData || needData.length == 0 || null == data || data.length == 0) {
return null;
}
KeySpec ks = new DESedeKeySpec(key);
SecretKeyFactory kf = SecretKeyFactory.getInstance("desede");
SecretKey ky = kf.generateSecret(ks);
Cipher c = Cipher.getInstance(TRI_DES);
c.init(Cipher.ENCRYPT_MODE, ky, new IvParameterSpec(keyIv));
return c.doFinal(needData);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 3des解密
*
* @param data
* 密文数据 16进制且长度为16的整数倍
* @return 明文数据
*/
private static byte[] union3DesDecrypt(byte data[]) {
try {
byte[] needData = initData(data);
if (null == needData || needData.length == 0 || null == data || data.length == 0) {
return null;
}
KeySpec ks = new DESedeKeySpec(key);
SecretKeyFactory kf = SecretKeyFactory.getInstance("desede");
SecretKey ky = kf.generateSecret(ks);
Cipher c = Cipher.getInstance(TRI_DES);
c.init(Cipher.DECRYPT_MODE, ky, new IvParameterSpec(keyIv));
return c.doFinal(needData);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 加密,加密完成之后,再用base64编码
*
* @param data
* 需要加密的明文数据
* @return 十六进制的加密数据
*/
public static String encrypt(String data) {
if (StringUtils.isEmpty(data)) {
return null;
}
try {
byte[] encData = union3DesEncrypt(data.getBytes("UTF-8"));
if (null != encData) {
return new BASE64Encoder().encode(encData);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 解密,先用base64转码,再解密
*
* @param data
* 十六进制的加密数据
* @return 明文数据
*/
public static String decrypt(String data) {
if (StringUtils.isEmpty(data)) {
return null;
}
try {
byte[] base64Byte = new BASE64Decoder().decodeBuffer(data);
byte[] sourData = union3DesDecrypt(base64Byte);
if (null != sourData) {
return new String(sourData, "UTF-8");
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "9cki59fysrlkeis990236e8d";
String keyIv = "1yd45d58";
TripleDESUtil.init(key, keyIv);
// 明文
String data = "test1";
System.out.println("明文:'" + data + "';长度:" + data.length());
System.out.println("明文字节:" + HexUtil.byte2hex(data.getBytes()));
// 加密
String encrypt = TripleDESUtil.encrypt(data);
System.out.println("加密后密文:'" + encrypt + "'");
// 解密
String decryptData = TripleDESUtil.decrypt(encrypt);
System.out.println("解密后明文:'" + decryptData + "';长度:" + decryptData.length());
System.out.println("解密后明文字节:" + HexUtil.byte2hex(decryptData.getBytes()));
// 解密完整后获取最终的值,trim()即可
String decryptDataFinal = decryptData.trim();
System.out.println("解密后明文(最终):'" + decryptDataFinal + "';长度:" + decryptDataFinal.length());
System.out.println("解密后明文字节(最终):" + HexUtil.byte2hex(decryptDataFinal.getBytes()));
}
}
输出结果:
注意事项:
1,使用CBC的加密模式一定要有一个初始化向量的值
2,使用了填充模式,注意获取结果之后要trim以下。否则字符串后面会有\u0000的空字符
-------------------------------------------------------------分割线-------------------------------------------------------------------
下面再介绍下工作中用到的SHA256,MD5好像是说安全性越来越差了。
关于一致性的校验,是没有密钥的,通过不可逆的算法对数据data进行计算,最终得到一串唯一的值。在工作中经常使用这个算法来验证数据的一致性,比如:
常见的数据格式:{"key1":"value1", "key2":"value2", "hashKey":"hashValue"}
客户端A通过key1和key2的值拼接成明文:key1=value1&key2=value2 来计算出来的hashValue,一起拼接好传给服务器B。
服务器B接收到这串数据,也通过同样的算法通过key1和key2的值来计算hashKey2,在验证hashKey是否等于hashKey2来判断改组数据是不是被第三方恶意更改过,确保一致性。
此时就会产生一个问题:一般hash验证只有几种,MD5或者sha356等。如果伪装服务器C通过技术截取到客户端A的数据,并且知道明文的规则是key1=value1&key2=value2,很有有可能进行篡改获取到{"key1":"value1new", "key2":"value2new", "hashKey":"hashValue2"},也通过相同的一致性校验发送给服务器B,以此破解。
最常用的方法就是,客户端A和服务器B双方约定好一个密钥key,计算明文规则改成:key1=value1&key2=value2&key。此时不通过其他的改动就可以提升整个一个非常大的档次