Java加密算法 AES

2018-03-21 22:28:02
package com.stone.security;

import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

/**
 * AES 算法 对称加密,密码学中的高级加密标准 2005年成为有效标准 
 * @author stone
 * @date 2014-03-10 06:49:19
 */
public class AES {
	static Cipher cipher;
	static final String KEY_ALGORITHM = "AES";
	static final String CIPHER_ALGORITHM_ECB = "AES/ECB/PKCS5Padding";
	/*
	 * 
	 */
	static final String CIPHER_ALGORITHM_CBC = "AES/CBC/PKCS5Padding";
	/* 
	 * AES/CBC/NoPadding 要求
	 * 密钥必须是16字节长度的;Initialization vector (IV) 必须是16字节
	 * 待加密内容的字节长度必须是16的倍数,如果不是16的倍数,就会出如下异常:
	 * javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
	 * 
	 *  由于固定了位数,所以对于被加密数据有中文的, 加、解密不完整
	 *  
	 *  可 以看到,在原始数据长度为16的整数n倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,
	 *  其它情况下加密数据长 度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],
	 *  除了NoPadding填充之外的任何方 式,加密数据长度都等于16*(n+1).
	 */
	static final String CIPHER_ALGORITHM_CBC_NoPadding = "AES/CBC/NoPadding"; 
	
	static SecretKey secretKey;
		
	public static void main(String[] args) throws Exception {
		method1("a*jal)k32J8czx囙国为国宽");
		method2("a*jal)k32J8czx囙国为国宽");
		method3("a*jal)k32J8czx囙国为国宽");
		
		method4("123456781234囙为国宽");// length = 16
		method4("12345678abcdefgh");// length = 16
		
	}
	
	/**
	 * 使用AES 算法 加密,默认模式  AES/ECB
	 */
	static void method1(String str) throws Exception {
		cipher = Cipher.getInstance(KEY_ALGORITHM);
		//KeyGenerator 生成aes算法密钥
		secretKey = KeyGenerator.getInstance(KEY_ALGORITHM).generateKey();
		System.out.println("密钥的长度为:" + secretKey.getEncoded().length);
		
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);//使用加密模式初始化 密钥
		byte[] encrypt = cipher.doFinal(str.getBytes()); //按单部分操作加密或解密数据,或者结束一个多部分操作。
		
		System.out.println("method1-加密:" + Arrays.toString(encrypt));
		cipher.init(Cipher.DECRYPT_MODE, secretKey);//使用解密模式初始化 密钥
		byte[] decrypt = cipher.doFinal(encrypt);
		System.out.println("method1-解密后:" + new String(decrypt));
		
	}
	
	/**
	 * 使用AES 算法 加密,默认模式 AES/ECB/PKCS5Padding
	 */
	static void method2(String str) throws Exception {
		cipher = Cipher.getInstance(CIPHER_ALGORITHM_ECB);
		//KeyGenerator 生成aes算法密钥
		secretKey = KeyGenerator.getInstance(KEY_ALGORITHM).generateKey();
		System.out.println("密钥的长度为:" + secretKey.getEncoded().length);
		
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);//使用加密模式初始化 密钥
		byte[] encrypt = cipher.doFinal(str.getBytes()); //按单部分操作加密或解密数据,或者结束一个多部分操作。
		
		System.out.println("method2-加密:" + Arrays.toString(encrypt));
		cipher.init(Cipher.DECRYPT_MODE, secretKey);//使用解密模式初始化 密钥
		byte[] decrypt = cipher.doFinal(encrypt);
		System.out.println("method2-解密后:" + new String(decrypt));
		
	}
	
	static byte[] getIV() {
		String iv = "1234567812345678"; //IV length: must be 16 bytes long
		return iv.getBytes();
	}
	
	/**
	 * 使用AES 算法 加密,默认模式 AES/CBC/PKCS5Padding
	 */
	static void method3(String str) throws Exception {
		cipher = Cipher.getInstance(CIPHER_ALGORITHM_CBC);
		//KeyGenerator 生成aes算法密钥
		secretKey = KeyGenerator.getInstance(KEY_ALGORITHM).generateKey();
		System.out.println("密钥的长度为:" + secretKey.getEncoded().length);
		
		cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(getIV()));//使用加密模式初始化 密钥
		byte[] encrypt = cipher.doFinal(str.getBytes()); //按单部分操作加密或解密数据,或者结束一个多部分操作。
		
		System.out.println("method3-加密:" + Arrays.toString(encrypt));
		cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(getIV()));//使用解密模式初始化 密钥
		byte[] decrypt = cipher.doFinal(encrypt);
		System.out.println("method3-解密后:" + new String(decrypt));
		
	}
	
	/**
	 * 使用AES 算法 加密,默认模式 AES/CBC/NoPadding  参见上面对于这种mode的数据限制
	 */
	static void method4(String str) throws Exception {
		cipher = Cipher.getInstance(CIPHER_ALGORITHM_CBC_NoPadding);
		//KeyGenerator 生成aes算法密钥
		secretKey = KeyGenerator.getInstance(KEY_ALGORITHM).generateKey();
		System.out.println("密钥的长度为:" + secretKey.getEncoded().length);
		
		cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(getIV()));//使用加密模式初始化 密钥
		byte[] encrypt = cipher.doFinal(str.getBytes(), 0, str.getBytes().length); //按单部分操作加密或解密数据,或者结束一个多部分操作。
		
		System.out.println("method4-加密:" + Arrays.toString(encrypt));
		cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(getIV()));//使用解密模式初始化 密钥
		byte[] decrypt = cipher.doFinal(encrypt);
		
		System.out.println("method4-解密后:" + new String(decrypt));
		
	}
	
}


使用32字节(256位)密钥 

前文,method1-4,打印出来的 密钥长度都是16字节长度;都是通过KeyGenerator自动生成的。

jdk中 AES中的Cipher,对应的密钥长度为128位的,按8bits一个字节算,整好就是16个字节。



如果要指定为32字节长度(256位)密钥,如下实现:


[java] view plain copy

  1. /** 

  2.  * 指定密钥长度为32位字节。 默认只能16位,是因为美国的出口政策限制。 

  3.  * @param key_32 

  4.  * @param str 

  5.  * @throws Exception 

  6.  */  

  7. static void method5(String key_32, String str) throws Exception {  

  8.     System.out.println();  

  9.     cipher = Cipher.getInstance(CIPHER_ALGORITHM_ECB);  

  10.     secretKey = new SecretKeySpec(key_32.getBytes("utf-8"), "AES");  

  11.     System.out.println("密钥的长度为:" + secretKey.getEncoded().length);  

  12.   

  13.     cipher.init(Cipher.ENCRYPT_MODE, secretKey);  

  14.     byte[] encrypt = cipher.doFinal(str.getBytes(), 0, str.getBytes().length);  

  15.   

  16.     System.out.println("method5-加密:" + Arrays.toString(encrypt));  

  17.     cipher.init(Cipher.DECRYPT_MODE, secretKey);//使用解密模式初始化 密钥  

  18.     byte[] decrypt = cipher.doFinal(encrypt);  

  19.     System.out.println("method5-解密后:" + new String(decrypt, "utf-8"));  

  20. }  


调用代码:

[java] view plain copy

  1. String key_32 = "0123456789abcdef0123456789ABCDEF";  

  2. method5(key_32, "123456781234囙为国宽");  


如上,通过SecretkeySpec,可以指定32位字节密钥。

但是,运行后,发现报错了:"java.security.InvalidKeyException: Illegal key size or default parameters…"

网络搜索后发现如下解决方法:http://blog.csdn.net/l1028386804/article/details/42706039

因我的环境是jdk8,在oracle搜索到jce-jdk8的下载页面:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html


最后,大功告成:


[plain] view plain copy

  1. 密钥的长度为:32  

  2. method5-加密:[-9, -118, -4, 2, 76, 48, 60, 4, -100, 19, 24, -14, 93, -20, -19, 77, -79, 105, -78, 53, 17, 29, -65, 36, 104, -60, 83, 31, -76, -26, 54, -120]  

  3. method5-解密后:123456781234囙为国宽  


如果像安卓app,要在客户端实现32位字节密钥加、解密,估计还是不行的;只能考虑自己实现一套AES的算法了,同时服务端也使用相应算法



使用"AES/CBC/NoPadding"引发的问题


不考虑上面的256位密钥问题,在默认情况下,这种padding,要求 密钥、IV长度都是16字节,内容字节长度必须是16的倍数,如果不是16的倍数,则引发异常。

如果一定要使用这种padding,而无法保证内容为16字节的倍数,可以考虑自实现一套规则,补全内容长度为16*n


  • 2018-04-03 10:21:35

    jquery实时监听输入框值变化

    在做web开发时候很多时候都需要即时监听输入框值的变化,以便作出即时动作去引导浏览者增强网站的用户体验感。而采用onchange时间又往往是在输入框失去焦点(onblur)时候触发,有时候并不能满足条件。

  • 2018-04-03 10:22:20

    JQuery如何监听DIV内容变化

    这几天在做一个微博的接入,需要判断微博是否被关注,要检查微博标签的DIV是否有“已关注”的字符,但这个DIV的内容是微博JSSDK动态生成。$("#id").html()是获取不到我想要的内容。原因是当我们获取的时候内容还没有改变,所以获取不到,如果就想到监听这个DIV内容变化后,再来获取就个时候就能获取到了。于是产生新的问题,如何监听DIV的变化?

  • 2018-04-04 23:52:03

    PowerManager之PowerManager

    当你在做一些事情时,如果持续时间过长,那么一段时间后屏幕会灭掉,如果你想在你做这些事时屏幕始终保持点亮状态,那么你需要WakeLock的帮助。

  • 2018-04-07 23:35:16

    使用Intent传递对象的两种方式

    Intent 的用法相信你已经比较熟悉了,我们可以借助它来启动活动、发送广播、启动服务等。在进行上述操作的时候,我们还可以在Intent 中添加一些附加数据,以达到传值的效果,比如在FirstActivity 中添加如下代码:

  • 2018-04-10 14:59:59

    JS实现数组去重方法总结(六种方法)

    这篇文章给大家总结下JS实现数组去重方法(六种方法),面试中也经常会遇到这个问题。文中给大家引申的还有合并数组并去重的方法,感兴趣的朋友跟随脚本之家小编一起学习吧

  • 2018-04-13 17:28:33

    jsoup 使用总结4--高级用法之 script js 脚本

    大部分时候,我们使用jsoup解析网页的时候,都是直接找到某一类元素,或者按某种selector查询;具体使用方法可以参考jsoup官网文档 那么你有没有实际操作过,查找script js 脚本呢,因为很多时候页面的内容是根据js动态生成的,或者数据是动态变更;那么这个时候,我们只是获取html页面中script js脚本之间的内容。