浅析Java中使用AES对称加密步骤解析、SpringBoot如何实现AES加解密(秘钥、偏移量)、Java AES加解密工具类参考示例
一、Java 使用 AES 步骤解析
AES是一种对称的加密算法,可基于相同的密钥进行加密和解密。Java采用AES算法进行加解密的逻辑大致如下:(1)生成/获取密钥;(2)加/解密。
(一)关于密钥步骤
1、生成密钥
密钥的生成是通过KeyGenerator来生成的。通过获取一个KeyGenerator实例,然后调用其generateKey()方法即可生成一个SecretKey对象。大致逻辑一般如下:
private SecretKey geneKey() throws Exception {
//获取一个密钥生成器实例
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
SecureRandom random = new SecureRandom();
random.setSeed("123456".getBytes());//设置加密用的种子,密钥
keyGenerator.init(random);
SecretKey secretKey = keyGenerator.generateKey();
return secretKey;
}
上述生成密钥的过程中指定了固定的种子,每次生成出来的密钥都是一样的。
还有一种形式,我们可以通过不指定SecureRandom对象的种子,即不调用其setSeed方法,这样每次生成出来的密钥都可能是不一样的。
random.setSeed("123456".getBytes());//设置加密用的种子,密钥
// 即把这段代码去掉即可
通过KeyGenerator的init(keySize)方法进行初始化,而不是通过传递SecureRandom对象进行初始化也可以达到上面的效果,每次生成的密钥都可能是不一样的。但是对应的keySize的指定一定要正确,AES算法的keySize是128。
private SecretKey geneKey() throws Exception {
//获取一个密钥生成器实例
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
return secretKey;
}
但是这种每次生成出来的密钥都是不同的情况下,我们需要把加密用的密钥存储起来,以供解密的时候使用,不然就没法进行解密了。
2、密钥的存储
密钥SecretKey里面最核心的内容就是其中的密钥对应的字节数组,可以通过SecretKey的getEncoded()方法获取。然后把它存储起来即可。最简单的方式就是直接写入一个文件中。
//把上面的密钥存起来
Path keyPath = Paths.get("D:/aes.key");
Files.write(keyPath, secretKey.getEncoded());
3、获取存储的密钥
获取存储的密钥的核心是把密钥的字节数组转换为对应的SecretKey。这可以通过SecretKeySpec来获取,其实现了SecretKey接口,然后构造参数里面将接收密钥的字节数组。
private SecretKey readKey(Path keyPath) throws Exception {
//读取存起来的密钥
byte[] keyBytes = Files.readAllBytes(keyPath);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, ALGORITHM);
return keySpec;
}
(二)关于加解密步骤
Java采用AES算法进行加解密的过程是类似的,具体如下:
1、指定算法,获取一个Cipher实例对象
Cipher cipher = Cipher.getInstance(ALGORITHM);//算法是AES
2、生成 / 读取用于加解密的密钥
SecretKey secretKey = this.geneKey();
3、用指定的密钥初始化Cipher对象,同时指定加解密模式,是加密模式还是解密模式。
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
4、通过update指定需要加密的内容,不可多次调用。
cipher.update(content.getBytes());
5、通过Cipher的dofinal()进行最终的加解密操作。
byte[] result = cipher.doFinal();//加密后的字节数组
通过以上几步就完成了使用AES算法进行加解密的操作了。其实第4、5步是可以合在一起的,即在进行doFinal的时候传递需要进行加解密的内容,如下:
byte[] result = cipher.doFinal(content.getBytes()); // 4、5合并就这样写即可
但是如果update指定了加密的内容,而doFinal的时候也指定了加密的内容,那最终加密出来的结果将是两次指定的加密内容的和对应的加密结果。
二、使用对称加密方式(AES)实践
AES 加解密工具类:ECB 模式,不支持使用偏移向量
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.apache.commons.codec.binary.Base64;
import java.util.HashMap;
import java.util.Map;
// 前后端数据传输加密工具类
public class AesEncryptUtils {
//可配置到Constant中,并读取配置文件注入,16位,自己定义
private static final String KEY = "xxxxxxxxxxxxxxxx";
//参数分别代表 算法名称/加密模式/数据填充方式
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
/* 加密
* @param content 加密的字符串
* @param encryptKey key值*/
public static String encrypt(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
byte[] b = cipher.doFinal(content.getBytes("utf-8"));
// 采用base64算法进行转码,避免出现中文乱码
return Base64.encodeBase64String(b);
}
/* 解密
* @param encryptStr 解密的字符串
* @param decryptKey 解密的key值*/
public static String decrypt(String encryptStr, String decryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
// 采用base64算法进行转码,避免出现中文乱码
byte[] encryptBytes = Base64.decodeBase64(encryptStr);
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
public static String encrypt(String content) throws Exception {
return encrypt(content, KEY);
}
public static String decrypt(String encryptStr) throws Exception {
return decrypt(encryptStr, KEY);
}
}
三、Java实现AES加密(秘钥、偏移量)
AES 加解密工具类:CBC 模式,使用偏移向量
package com.unicom.atlas.statistic.abnormal.table.aes;
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
public class AESUtil {
private static final String KEY = "******"; //AES 16位
public static final String VIPARA = "1234567890abcdef"; //AES 128位数据块对应偏移量为16位
//AES:加密方式 CBC:工作模式 PKCS5Padding:填充模式
private static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding";
private static final String AES = "AES";
public static final String CODE_TYPE = "UTF-8"; // 编码方式
/* AES 加密操作
* @param content 待加密内容
* @param key 加密密钥
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String key) {
if (content == null || "".equals(content)) {
return content;
}
try {
/*
* 新建一个密码编译器的实例,由三部分构成,用"/"分隔,分别代表如下
* 1. 加密的类型(如AES,DES,RC2等)
* 2. 模式(AES中包含ECB,CBC,CFB,CTR,CTS等)
* 3. 补码方式(包含nopadding/PKCS5Padding等等)
* 依据这三个参数可以创建很多种加密方式
*/
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
//偏移量
IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes(CODE_TYPE));
byte[] byteContent = content.getBytes(CODE_TYPE);
//使用加密秘钥
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CODE_TYPE), AES);
//SecretKeySpec skeySpec = getSecretKey(key);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, zeroIv);// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
return Base64.encodeBase64String(result);//通过Base64转码返回
} catch (Exception ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/* AES 解密操作
* @param content
* @param key
*/
public static String decrypt(String content, String key) {
if (content == null || "".equals(content)) {
return content;
}
try {
//实例化
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
IvParameterSpec zeroIv = new IvParameterSpec(VIPARA.getBytes(CODE_TYPE));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(CODE_TYPE), AES);
//SecretKeySpec skeySpec = getSecretKey(key);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, zeroIv);
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result, CODE_TYPE);
} catch (Exception ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
// 生成加密秘钥
private static SecretKeySpec getSecretKey(final String key) {
//返回生成指定算法密钥生成器的 KeyGenerator 对象
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(AES);
//AES 要求密钥长度为 128
kg.init(128, new SecureRandom(key.getBytes()));
//生成一个密钥
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), AES);// 转换为AES专用密钥
} catch (Exception ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}