java 使用AES解密报这个异常,字面理解很容易,就是解密的字符串的数组必须是16的倍数
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:922)1.使用的代码:
package com.symmetric.aes;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class TestAES2 {
private static final String enType_AES = "AES";
private static final String pathStr = "D://aes.key";
private static final String testStr = "AES tips you shoutld try again";
public static void main(String[] args) {
SecretKey genSecretKey = testGenerateKey();
String string = genSecretKey.toString();
// byte[] testEncryptBytes = testEncryptBytes(testStr, genSecretKey);
// String testDecode = testDecrptBytes(testEncryptBytes, genSecretKey);
// System.out.println(testDecode); //直接操作数组,加密解密正常
String testEncrypt = testEncrypt(testStr, genSecretKey);
String testDecode2 = testDecrpt(testEncrypt, genSecretKey);
System.out.println(testDecode2);
}
/**
* 生成密钥
* 通过传递SecureRandom对象进行初始化
* 不指定种子或密钥
* @return
*/
public static SecretKey testGenerateKey(){
SecretKey genSecretKey = null;
try {
KeyGenerator kGenerator = KeyGenerator.getInstance(enType_AES);
SecureRandom sRandom = new SecureRandom();
kGenerator.init(sRandom);//不使用种子,每次生成的都不同
genSecretKey = kGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return genSecretKey;
}
/**
* 加密
* @param str
* @param genSecretKey
* @return
*/
public static byte[] testEncryptBytes(String str,SecretKey genSecretKey){
byte encrypt [] = null;
try {
Cipher cipher = Cipher.getInstance(enType_AES);
cipher.init(Cipher.ENCRYPT_MODE, genSecretKey);//加密模式,密钥
//cipher.update(str.getBytes());
encrypt = cipher.doFinal(str.getBytes());
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return encrypt;
}
public static String testEncrypt(String str,SecretKey genSecretKey){
byte[] testEncryptBytes = testEncryptBytes(str, genSecretKey);
System.out.println(Arrays.toString(testEncryptBytes));
System.out.println("加密后的数组长度"+testEncryptBytes.length);
return (new String(testEncryptBytes));
}
/**
* 解密
* @param str
* @param genSecretKey
* @return
*/
public static String testDecrptBytes(byte[] bytes,SecretKey genSecretKey){
String decoderStr = null;
try {
Cipher cipher = Cipher.getInstance(enType_AES);
cipher.init(Cipher.DECRYPT_MODE, genSecretKey);//解密模式
//cipher.update(str.getBytes());
byte[] doFinal = cipher.doFinal(bytes);
decoderStr = new String(doFinal);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return decoderStr;
}
public static String testDecrpt(String str,SecretKey genSecretKey){
System.out.println(Arrays.toString(str.getBytes()));
System.out.println("数组的大小"+str.getBytes().length);
return testDecrptBytes(str.getBytes(), genSecretKey);
}
}
2.分析出现此异常的情况:
如果不把加密后的数组拼接为字符串,直接返回,然后使用这个加密后的数组进行解密没有任何错误
但是把加密后的数组拼接为字符串,然后解密时在把此字符串转为数组,就会出现此异常
3.具体分析:
发现当把字节数组转为字符串后,在把字符串.getBytes()获得数组,发现两个字节数组前后不一样了,,这是报错的位置所在
声明:new String(byte[]),和"str".getBytes(),两个方法使用的编码一样,然后换成其他编码也出现这样情况
3.原因:
3.1. 为什么数组转字符串,字符串然后转数组会出现,前后两个字节数组的值会不同,因为并不是每个字节数和编码集上的字符都有对应关系,如果一个字节数在编码集上没有对应,编码new String(byte[]) 后,往往解出来的会是一些乱码无意义的符号:例如:��,
但是解码的时候,�这个字符也是一个字符在编码表中也有固定的字节数用来表示,所有解码出来的值必定是编码表中对应的值,除非你的字节数组中的字节数正好在编码表中有对应的值,否则编码,解码后的字节数组会不一样
误区: 误以为所有的字节数组都可以new String(),然后在通过String.getBytes()还原
3.2.再说这个异常报:解密的字节数组必须是16的倍数,这得从AES的原理说起,AES是把数据按16字节分组加密的,所有如果数组长度不是16的倍数会报错
AES原理:AES是对数据按128位,也就是16个字节进行分组进行加密的,每次对一组数据加密需要运行多轮。而输入密钥的长度可以为128、192和256位,也就是16个字节、24个字节和32个字节,如果用户输入的密钥长度不是这几种长度,也会补成这几种长度。无论输入密钥是多少字节,加密还是以16字节的数据一组来进行的,密钥长度的不同仅仅影响加密运行的轮数。
4.解决的办法:
4.1 可以用base64对参生的数组进行编码,然后在解码,这样不会像new String(byte[]),getBytes()那样造成数组前后不一致,一开始,我看到大部分人都是用base64,我也只是以为多一层编码看起来安全一些而已,没想到base64对数组的处理是不会造成误差的
4.2 就是直接返回数组,然后再用数组解密咯