Advanced Encryption Standard缩写:AES,译为高级加密标准。
AES是用于取代DES的对称加密算法,既然有对称加密,那么会有非对称加密,常见的非对称加密有RSA加密。
对称加密即为只有一个公钥,数据加密者和数据解密者共有一个公钥,可使用公钥完成数据的加密和解密,密钥由双方商定共同保管。而非对称加密的密钥可分为公钥和私钥,私钥用于数据的加密,公钥用于数据的解密,公私钥的其中一方无法完成数据的加密和解密,且加密后的数据无法被反解密。
因此,对于安全性而言,显而易见的是非对称加密更加安全,但对称加密效率更高。
本篇文章的主要内容是AES对称加密。
前置条件:
设加密函数为E,则有
C = E(K,P)
所以,密文是由明文P作为入参经过密钥K加密特定轮数得到。
设解密函数为D,则有
P = D (K, C)
所以,密文解密是由密文C和密钥K作为解密入参经解密函数得到。
AES密钥可由Hex生成
// 使用密钥生成器 KeyGenerator 生成的 16 字节随机密钥的 hex 字符串,使用时解 hex 得到二进制密钥
byte[] bytes = AesEncryptUtil.initKey();
String aesKey = Hex.encodeHexString(bytes);
SpringBoot整合AES加密步骤
AesEncryptUtil.class
/**
* AES 加/解密工具类
* 使用密钥时请使用 initKey() 方法来生成随机密钥
* initKey 方法内部使用 java.crypto.KeyGenerator 密钥生成器来生成特定于 AES 算法参数集的随机密钥
*/
public class AesEncryptUtil {
private AesEncryptUtil() {
}
/**
* 密钥算法类型
*/
public static final String KEY_ALGORITHM = "AES";
/**
* 密钥的默认位长度
*/
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding";
public static final String ECB_NO_PADDING = "AES/ECB/NoPadding";
public static String base64Encode(byte[] bytes) {
return Base64.encodeBase64String(bytes);
}
public static byte[] base64Decode(String base64Code) {
return Base64.decodeBase64(base64Code);
}
public static byte[] aesEncryptToBytes(String content, String hexAesKey) throws Exception {
return encrypt(content.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(hexAesKey.toCharArray()));
}
public static String aesEncrypt(String content, String hexAesKey) throws Exception {
return base64Encode(aesEncryptToBytes(content, hexAesKey));
}
public static String aesDecryptByBytes(byte[] encryptBytes, String hexAesKey) throws Exception {
byte[] decrypt = decrypt(encryptBytes, Hex.decodeHex(hexAesKey.toCharArray()));
return new String(decrypt, StandardCharsets.UTF_8);
}
public static String aesDecrypt(String encryptStr, String hexAesKey) throws Exception {
return aesDecryptByBytes(base64Decode(encryptStr), hexAesKey);
}
/**
* 生成 Hex 格式默认长度的随机密钥
* 字符串长度为 32,解二进制后为 16 个字节
*
* @return String Hex 格式的随机密钥
*/
public static String initHexKey() {
return Hex.encodeHexString(initKey());
}
/**
* 生成默认长度的随机密钥
* 默认长度为 128
*
* @return byte[] 二进制密钥
*/
public static byte[] initKey() {
return initKey(DEFAULT_KEY_SIZE);
}
/**
* 生成密钥
* 128、192、256 可选
*
* @param keySize 密钥长度
* @return byte[] 二进制密钥
*/
public static byte[] initKey(int keySize) {
// AES 要求密钥长度为 128 位、192 位或 256 位
if (keySize != 128 && keySize != 192 && keySize != 256) {
throw new RuntimeException("error keySize: " + keySize);
}
// 实例化
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("no such algorithm exception: " + KEY_ALGORITHM, e);
}
keyGenerator.init(keySize);
// 生成秘密密钥
SecretKey secretKey = keyGenerator.generateKey();
// 获得密钥的二进制编码形式
return secretKey.getEncoded();
}
/**
* 转换密钥
*
* @param key 二进制密钥
* @return Key 密钥
*/
private static Key toKey(byte[] key) {
// 实例化 DES 密钥材料
return new SecretKeySpec(key, KEY_ALGORITHM);
}
/**
* 加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密的数据
*/
public static byte[] encrypt(byte[] data, byte[] key) {
return encrypt(data, key, ECB_PKCS_5_PADDING);
}
/**
* 加密
*
* @param data 待加密数据
* @param key 密钥
* @param cipherAlgorithm 算法/工作模式/填充模式
* @return byte[] 加密的数据
*/
public static byte[] encrypt(byte[] data, byte[] key, final String cipherAlgorithm) {
// 还原密钥
Key k = toKey(key);
try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
// 初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, k);
// 发现使用 NoPadding 时,使用 ZeroPadding 填充
if (ECB_NO_PADDING.equals(cipherAlgorithm)) {
return cipher.doFinal(formatWithZeroPadding(data, cipher.getBlockSize()));
}
// 执行操作
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException("AES encrypt error", e);
}
}
/**
* 解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密的数据
*/
public static byte[] decrypt(byte[] data, byte[] key) {
return decrypt(data, key, ECB_PKCS_5_PADDING);
}
/**
* 解密
*
* @param data 待解密数据
* @param key 密钥
* @param cipherAlgorithm 算法/工作模式/填充模式
* @return byte[] 解密的数据
*/
public static byte[] decrypt(byte[] data, byte[] key, final String cipherAlgorithm) {
// 还原密钥
Key k = toKey(key);
try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
// 初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, k);
// 发现使用 NoPadding 时,使用 ZeroPadding 填充
if (ECB_NO_PADDING.equals(cipherAlgorithm)) {
return removeZeroPadding(cipher.doFinal(data), cipher.getBlockSize());
}
// 执行操作
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException("AES decrypt error", e);
}
}
private static byte[] formatWithZeroPadding(byte[] data, final int blockSize) {
final int length = data.length;
final int remainLength = length % blockSize;
if (remainLength > 0) {
byte[] inputData = new byte[length + blockSize - remainLength];
System.arraycopy(data, 0, inputData, 0, length);
return inputData;
}
return data;
}
private static byte[] removeZeroPadding(byte[] data, final int blockSize) {
final int length = data.length;
final int remainLength = length % blockSize;
if (remainLength == 0) {
// 解码后的数据正好是块大小的整数倍,说明可能存在补 0 的情况,去掉末尾所有的 0
int i = length - 1;
while (i >= 0 && 0 == data[i]) {
i--;
}
byte[] outputData = new byte[i + 1];
System.arraycopy(data, 0, outputData, 0, outputData.length);
return outputData;
}
return data;
}
public static void main(String[] args) throws Exception {
byte[] bytes = AesEncryptUtil.initKey();
// 使用密钥生成器 KeyGenerator 生成的 16 字节随机密钥的 hex 字符串,使用时解 hex 得到二进制密钥
String aesKey = Hex.encodeHexString(bytes);
System.out.println(aesKey);
// String aesKey = "d86d7bab3d6ac01ad9dc6a897652f2d2";
String content = "你好";
System.out.println("加密前:" + content);
String encrypt = aesEncrypt(content, aesKey);
System.out.println(encrypt.length() + ":加密后:" + encrypt);
String decrypt = aesDecrypt(encrypt, aesKey);
System.out.println("解密后:" + decrypt);
}
}
SecurityParameter.class
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {
/**
* 入参是否解密,默认解密
* @return
*/
boolean inDecode() default true;
/**
* 出参是否加密,默认加密
* @return
*/
boolean outEncode() default true;
}
加解密请求体处理
定义全局处理:
请求:拿密文来请求(request)到接口时,我们先在请求到接口前做一步解密处理,即:DecodeRequestBodyAdvice
返回:接口数据在Controller返回之后,在请求体中(Response)对数据先进行加密处理用于脱敏,即
EncodeResponseBodyAdvice
DecodeRequestBodyAdvice.class
@Slf4j
@ControllerAdvice(basePackages = "com.ltx.aes_demo.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
try {
boolean encode = false;
if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//入参是否需要解密
encode = serializedField.inDecode();
}
if (encode) {
log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密");
return new MyHttpInputMessage(inputMessage);
} else {
return inputMessage;
}
} catch (Exception e) {
e.printStackTrace();
log.error("对方法method :【" + Objects.requireNonNull(methodParameter.getMethod()).getName() + "】返回数据进行解密出现异常:" + e.getMessage());
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
this.headers = inputMessage.getHeaders();
this.body = IOUtils.toInputStream(AesEncryptUtil.aesDecrypt(easpString(IOUtils.toString(inputMessage.getBody(), StandardCharsets.UTF_8)), AesConstant.aesKey));
}
@Override
public InputStream getBody(){
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
public String easpString(String requestData) {
if (StringUtils.isNotBlank(requestData)) {
String s = "{\"requestData\":";
if (!requestData.startsWith(s)) {
throw new RuntimeException("参数【requestData】缺失异常!");
} else {
int closeLen = requestData.length() - 1;
int openLen = "{\"requestData\":".length();
return StringUtils.substring(requestData, openLen, closeLen);
}
}
return "";
}
}
}
请求的json格式为:
{“requestData”:“0Wa795xrq/8Gp9pUKZcYgEOPreQvHXZJYhWoNH9bbuSfbTDjb65VdzHZEsx3FhRiysxU04KACg1gXFBla4WBggj5u4EjmVfUP3hotGOeY+M=”}
EncodeResponseBodyAdvice.class
@Slf4j
@ControllerAdvice(basePackages = "com.ltx.aes_demo.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
boolean encode = false;
if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//出参是否需要加密
encode = serializedField.outEncode();
/**
* 测试自定义注解,学习AES加解密可忽略删除此if
*/
if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(Decrypt.class)){
log.info("own @interface");
Decrypt decryptField = methodParameter.getMethodAnnotation(Decrypt.class);
boolean test = decryptField.test();
if (test){
log.info("奥里给!");
}
}
}
if (encode) {
log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
ObjectMapper objectMapper = new ObjectMapper();
try {
String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
return AesEncryptUtil.aesEncrypt(result, AesConstant.aesKey);
} catch (Exception e) {
e.printStackTrace();
log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
}
}
return body;
}
}
@SecurityParameter
@GetMapping("getUser")
public User getUser() {
return userService.getUserById();
}
@Decrypt
@SecurityParameter
@PostMapping("test")
public String test(@RequestBody User user) {
return "hello world";
}
@PostMapping("/save")
@ResponseBody
@SecurityParameter(outEncode = false)
public User save(@RequestBody User info) {
System.out.println(info);
return info;
}
写在最后
如果不明白自定义注解的使用请查看
Spring自定义注解常用元素
如果不明白全局统一异常处理请查看
ResponseBody、RequestBodyAdvice解读