AES256加解密比较常见,这里简单记录以供参考!
一、下载
官方JDK默认支持AES-128,在 1.8.0_151 和 1.8.0_152 版本之前,需要从Oracle官网下载安全JAR包方可支持AES-256,之后版本则可以通过在运行环境中设置以下属性启用AES-256支持!否则就会遇到这个错误:java.security.InvalidKeyException: Illegal key size。
Security.setProperty("crypto.policy", "unlimited");
JDK7下载:Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 7 Download
JDK8下载:JCE Unlimited Strength Jurisdiction Policy Files for JDK/JRE 8 Download
二、安装
下载的zip包里面有两个JAR文件,解压至JAVA运行环境安全目录(${JAVA_HOME}/jre/lib/security)替换local_policy.jar和US_export_policy.jar即可!
替换local_policy.jar和US_export_policy.jar
三、参数
AES涉及到5个关键词:分组密码体制, Padding, 初始向量IV, 密钥, 加密模式,这里任意一个参数值不同,最终的加解密结果都是不一样的。
-
分组密码体制: 所谓分组密码体制就是指将明文切成一段一段的来加密, 然后再把一段一段的密文拼起来形成最终密文的加密方式. AES采用分组密码体制, 即AES加密会首先把明文切成一段一段的, 而且每段数据的长度要求必须是128位16个字节, 如果最后一段不够16个字节了, 就需要用Padding来把这段数据填满16个字节, 然后分别对每段数据进行加密, 最后再把每段加密数据拼起来形成最终的密文.
-
Padding: Padding就是用来把不满16个字节的分组数据填满16个字节用的, 它有三种模式PKCS5、PKCS7和NOPADDING. PKCS5是指分组数据缺少几个字节, 就在数据的末尾填充几个字节的几, 比如缺少5个字节, 就在末尾填充5个字节的5. PKCS7是指分组数据缺少几个字节, 就在数据的末尾填充几个字节的0, 比如缺少7个字节, 就在末尾填充7个字节的0. NoPadding是指不需要填充, 也就是说数据的发送方肯定会保证最后一段数据也正好是16个字节. 那如果在PKCS5模式下, 最后一段数据的内容刚好就是16个16怎么办?那解密端就不知道这一段数据到底是有效数据还是填充数据了, 因此对于这种情况, PKCS5模式会自动帮我们在最后一段数据后再添加16个字节的数据, 而且填充数据也是16个16, 这样解密段就能知道谁是有效数据谁是填充数据了. PKCS7最后一段数据的内容是16个0, 也是同样的道理. 解密端需要使用和加密端同样的Padding模式, 才能准确的识别有效数据和填充数据. 我们开发通常采用PKCS7 Padding模式.
-
初始向量IV: 初始向量IV的作用是使加密更加安全可靠, 我们使用AES加密时需要主动提供初始向量, 而且只需要提供一个初始向量就够了, 后面每段数据的加密向量都是前面一段的密文. 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成.
-
密钥: AES要求密钥的长度可以是128位16个字节、192位或者256位, 位数越高, 加密强度自然越大, 但是加密的效率自然会低一些, 因此要做好衡量. 我们开发通常采用128位16个字节的密钥, 我们使用AES加密时需要主动提供密钥, 而且只需要提供一个密钥就够了, 每段数据加密使用的都是这一个密钥, 密钥来源为随机生成.
-
加密模式: AES一共有四种加密模式, 分别是ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB、OFB, 我们一般使用的是CBC模式. 四种模式中除了ECB相对不安全之外, 其它三种模式的区别并没有那么大. ECB模式是最基本的加密模式, 即仅仅使用明文和密钥来加密数据, 相同的明文块会被加密成相同的密文块, 这样明文和密文的结构将是完全一样的, 就会更容易被破解, 相对来说不是那么安全, 因此很少使用. CBC模式则比ECB模式多了一个初始向量IV, 加密的时候, 第一个明文块会首先和初始向量IV做异或操作, 然后再经过密钥加密, 然后第一个密文块又会作为第二个明文块的加密向量来异或, 依次类推下去, 这样相同的明文块加密出的密文块就是不同的, 明文的结构和密文的结构也将是不同的, 因此更加安全, 我们常用的就是CBC加密模式.
四、代码
/**
* @author xxx
* @date 2020-09-16 11:17
**/
public class AES256Util {
/**
* 密钥, 256位32个字节
*/
public static final String DEFAULT_SECRET_KEY = "uBdUx82vPHkDKb284d7NkjFoNcKWBuka";
private static final String AES = "AES";
/**
* 初始向量IV, 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成.
*/
private static final byte[] KEY_VI = "c558Gq0YQK2QUlMc".getBytes();
/**
* 加密解密算法/加密模式/填充方式
*/
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static java.util.Base64.Encoder base64Encoder = java.util.Base64.getEncoder();
private static java.util.Base64.Decoder base64Decoder = java.util.Base64.getDecoder();
static {
java.security.Security.setProperty("crypto.policy", "unlimited");
}
/**
* AES加密
*/
public static String encode(String key, String content) {
try {
javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));
// 获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);
// 根据密码器的初始化方式加密
byte[] byteAES = cipher.doFinal(byteEncode);
// 将加密后的数据转换为字符串
return base64Encoder.encodeToString(byteAES);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* AES解密
*/
public static String decode(String key, String content) {
try {
javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));
// 将加密并编码后的内容解码成字节数组
byte[] byteContent = base64Decoder.decode(content);
// 解密
byte[] byteDecode = cipher.doFinal(byteContent);
return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String dbPassword = "123456";
String encryptDbPwd = AES256Util.encode(DEFAULT_SECRET_KEY, dbPassword);
System.out.println("encrypt: " + encryptDbPwd);
String decrypt = AES256Util.decode(DEFAULT_SECRET_KEY, encryptDbPwd);
System.out.println("decrypt:" + decrypt);
}
}