前言
前面说到DES对称加解密算法的安全系数不是很高,企业项目中一般不会使用的。所以衍生了后续的3DES和AES算法,和DES一样,这两者都是对称加密算法。但是安全性比DES高很多,最近项目中集成的第三方兑换券就是用AES加密算法的,这里来和大家一起学习下AES加密算法。
AES加密算法Demo
直接上代码
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author 蒋墨风
* @title: AesTest
* @projectName study
* @description: AES加解密demo
* @date 2019/12/15 21:19
*/
public class AesTest {
/**
* @version V1.0
* @desc AES 加密工具类
*/
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法
/**
* AES 加密操作
*
* @param content 待加密内容
* @param password 加密密码
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String password) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
return Base64.encodeBase64String(result);//通过Base64转码返回
} catch (Exception ex) {
Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/**
* AES 解密操作
*
* @param content
* @param password
* @return
*/
public static String decrypt(String content, String password) {
try {
//实例化
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
//执行操作
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result, "utf-8");
} catch (Exception ex) {
Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/**
* 生成加密秘钥
*
* @return
*/
private static SecretKeySpec getSecretKey(final String password) {
//返回生成指定算法密钥生成器的 KeyGenerator 对象
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//AES 要求密钥长度为 128
kg.init(128, new SecureRandom(password.getBytes()));
//生成一个密钥
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public static void main(String[] args) {
String s = "爱琴孩的博客";
System.out.println("原始明文:" + s);
String s1 = AesTest.encrypt(s, "1236541");
System.out.println("加密之后的密文:" + s1);
System.out.println("解密之后的明文:" + AesTest.decrypt(s1, "123654"));
}
}
测试效果如下
密钥长度超过128
上面可以看到,我们代码密钥长度是128。如果想提高密钥长度为256,那么代码会报错的
kg.init(256, new SecureRandom(password.getBytes()));
报错信息如下
替换JCE类库
在Java的核心类库中有一个JCE(Java Cryptography Extension),JCE是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现,所以这个是实现加密解密的重要类库。在我们安装的JRE目录下有这样一个文件夹:%JAVE_HOME%\jre\lib\security,其中包含有两个.jar文件:“local_policy.jar ”和“US_export_policy.jar”,也就是我们平时说的jar包,这两个jar包就是我们JCE中的核心类库了。JRE中自带的“local_policy.jar ”和“US_export_policy.jar”是支持128位密钥的加密算法,而当我们要使用256位密钥算法的时候,已经超出它的范围,无法支持,所以才会报上面的错误。
解决方案:去官方下载JCE无限制权限策略文件。下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt。如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件。如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。目前项目用是jdk8,下面附上下载地址
JDK8的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
通过反射修改属性放开限制
/**
* 去除JCE限制
*/
private static void removeJceLimit() {
// 去除JCE加密限制,只限于Java1.8
try {
Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, false);
} catch (ClassNotFoundException | NoSuchFieldException |
SecurityException | IllegalArgumentException | IllegalAccessException ex) {
LOGGER.error("removeJceLimit e:", ex);
}
}
在服务启动类中执行这个方法放开限制,这样就不会报错了。
山不转水转
上面两种方式都有一定的缺陷,第一种如果直接替换生产环境jdk服务器上的jar,这样无疑影响了该服务器上的所有服务,哪怕是没有用到AES加密的服务,谁能担保这样换jar包不会对其他服务有影响。当然如果公司是土豪一台服务器只部署一个服务,这样就另当别论了。对于第二种方式,在启动类中通过反射放开限制,这样的风险也是不可预估的。总之生产环境还是要谨慎点比较好。除非你可以拍胸脯说,我对jdk了如指掌,就是这么自信。
那么最保险的方式是什么呢,我们可以将需要用到AES256加密的服务,指定一个单独的jdk启动,只替换这个jdk的local_policy.jar和US_export_policy.jar,这个单独的jdk不要在环境变量中配置,服务启动的时候我们指定这个jdk的路径。比如说说nohup /usr/server/jdk1.8.0_111/bin/java -jar test.jar &。这样就避免了因为替换Jar包影响了其他服务。