加密--java对加密的常见需求:3DES+BASE64Encoder+SHA256

概述:在信息交互过程中, 出于安全考虑,网络的传输中经常对传输数据做加密和编码处理,其中涉及以下几种: 
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通过key1key2的值拼接成明文:key1=value1&key2=value2 来计算出来的hashValue,一起拼接好传给服务器B。

服务器B接收到这串数据,也通过同样的算法通过key1key2的值来计算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。此时不通过其他的改动就可以提升整个一个非常大的档次







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值