新接口标准:所有敏感信息都必须使用AES的256位密钥加解密,发现测试在加密的时候报java.security.InvalidKeyException: Illegal key size错误。
产生错误原因:为了数据代码在传输过程中的安全,很多时候我们都会将要传输的数据进行加密,然后等对方拿到后再解密使用。我们在使用AES加解密的时候,在遇到128位密钥加解密的时候,没有进行什么特殊处理;然而,在使用256位密钥加解密的时候,如果不进行特殊处理的话,可能会因为jdk版本的问题出现这个异常java.security.InvalidKeyException: Illegal key size。
为什么会产生这样的错误?
我们做Java开发,都会先在电脑上安装JDK(Java Development Kit) 并配置环境变量,JDK中包含有JRE(Java Runtime Environment,即:Java运行环境),JRE中包括Java虚拟机(Java Virtual Machine)、Java核心类库和支持文件,而我们今天要说的主角就在Java的核心类库中。在Java的核心类库中有一个JCE(Java Cryptography Extension),JCE是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现,所以这个是实现加密解密的重要类库。
在我们安装的JRE目录下有这样一个文件夹:%JAVE_HOME%\jre\lib\security(%JAVE_HOME%是自己电脑的Java路径,),其中包含有两个.jar文件:“local_policy.jar ”和“US_export_policy.jar”,也就是我们平时说的jar包,这两个jar包就是我们JCE中的核心类库了。JRE中自带的“local_policy.jar ”和“US_export_policy.jar”是支持128位密钥的加密算法,而当我们要使用256位密钥算法的时候,已经超出它的范围,无法支持,所以才会报:“java.security.InvalidKeyException: Illegal key size or default parameters”的异常。
那么我们怎么解决呢?
首先进入%JAVE_HOME%/jre/lib/security/ 目录,看下目录里面是有一个 policy 文件夹,还是有local_policy.jar,
第一种情况:如果有policy 文件夹,说明此版本为JVM启用 无限制强度管辖策略 有了一种新的更简单的方法。
请在 当前文件夹中查找文件 java.security。
现在用文本编辑器打开java.security,并找到定义java安全性属性crypto.policy的行,它可以有两个值limited或unlimited - 默认值是limited。
默认情况下,您应该能找到一条注释掉的行:
#crypto.policy=unlimited
可以通过取消注释该行来启用无限制,删除#:
crypto.policy=unlimited
现在重新启动指向JVM的Java应用程序即可。
第二种情况:没有policy 文件夹,而是直接就有local_policy.jar,US_export_policy.jar两个jar包。
去官方下载JCE无限制权限策略文件。
下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
将两个jar文件放到%JAVE_HOME%\jre\lib\security目录下覆盖原来文件。
下载时若没有Oracle账户,这里提供一个账户网站。 OracleAccount
加密的问题迎刃而解,接下来是解密,关于解密其实是个小问题,关于Base64转换的问题而已
解密报错 Input length must be multiple of 16 when decrypting with padded cipher
于是乎回头看了一眼加密返回的代码似乎不对劲,直接返回byte数组了,只需要将字节数组byte[]转为base64字符串即可。
错误的返回
只是将byte[]数组toString了
return Arrays.toString(new Base64().encode(result));
正确转换
字节数组转为base64字符串
public String byteToBase64(byte[] b){
String image=Base64.getEncoder().encodeToString(b);
return image;
}
base64字符串转为字节数组
public byte[] base64ToByte(String base64str){
return Base64.getDecoder().decode(base64);
}
福利:AES256加解密工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
* @ClassName: AesEncryptUtil
* @Description: AES256加解密
* @Author: Richard_Chan
* @Create: 2022-01-06 16:57
*/
public class AesEncryptUtil {
//使用AES-256-CBC加密模式,key需要为32位,key和iv可以相同!
/**
* 加密方法
* @param data 要加密的数据
* @param key 加密key
* @param iv 加密iv
* @return 加密的结果
* @throws Exception
*/
public static String encrypt(String data, String key, String iv) throws Exception {
try {
SecretKeySpec keys = new SecretKeySpec(key.getBytes(), "AES");
//"算法/模式/补码方式"
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
// 偏移量
byte[] ivBytes = iv.getBytes();
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// 初始化AES密码器为加密器
cipher.init(Cipher.ENCRYPT_MODE, keys, ivSpec);
// 进行AES加密
byte[] result = cipher.doFinal(dataBytes);
return java.util.Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 解密方法
* @param data 要解密的数据
* @param key 解密key
* @param iv 解密iv
* @return 解密的结果
* @throws Exception
*/
public static String desEncrypt(String data, String key, String iv) throws Exception {
try {
byte[] encrypted1 = (byte[]) new Base64().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1); //执行操作
String originalString = new String(original);
return originalString;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 测试
*/
public static void main(String args[]) throws Exception {
// 携程与供应商协商授权用的用户名
String authUser = "test";
// 对user做md5(32位小写)
String key = Md5Helper.MD5(authUser);
// iv向量,取md5码后16位作为iv
String iv = key.substring(key.length() - 16);
// 被aes加密后的密文
String security = "88i6PIuXaQm7QbUBDut1AA==";
// 待脱敏的信息,以手机号为例说明
String mobile = "13800000000";
// 加密
String enData = encrypt(mobile, key, iv);
System.out.println(key);
System.out.println(iv);
System.out.println(enData);
// 解密
System.out.println(desEncrypt(enData, key, iv));
}
}