实现AES CBC模式后端加密 前端解密

1.封装加密返回体

package com.xxx.common.core.core.domain;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

@Data
@NoArgsConstructor
public class EncryptedR<T> {
private int code;
private String msg;
private String data; // 加密后的数据

private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY = "rK7CLfWzZqFmYlJQ6oVpXQ=="; // 必须是16字节
private static final String IV = "aB3#cD9!xY7@kL2$"; // 必须是16字节
public static EncryptedR error(String msg,int code) {
EncryptedR result = new EncryptedR<>();
result.code = code;
result.msg = msg;
return result;
}
/**
* 加密布尔值
*/
public static String encryptBoolean(boolean value) {
return encrypt(Boolean.toString(value));
}

/**
* 加密字符串(使用AES/CBC/PKCS5Padding)
*/
public static String encrypt(String plainText) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));

cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}

/**
* 解密字符串(使用AES/CBC/PKCS5Padding)
*/
public static String decrypt(String encryptedText) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));

cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decrypted = cipher.doFinal(decodedBytes);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失败", e);
}
}

public static void main(String[] args) throws NoSuchAlgorithmException {
String plainText = "123456";
String encrypted = encrypt(plainText);
System.out.println("加密结果:" + encrypted); // 输出类似:eYIQuRPS54iO2+qLft7GVQ==
System.out.println("解密结果:" + decrypt(encrypted));

KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128, new SecureRandom()); // AES-128
SecretKey secretKey = kg.generateKey();

String base64Key = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("Base64 Encoded SecretKey (16 bytes): " + base64Key);

// IV 可以随机生成
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
String ivBase64 = Base64.getEncoder().encodeToString(iv);
System.out.println("Base64 Encoded IV (16 bytes): " + ivBase64);

}

public static <T> EncryptedR<T> ok(T data) {
EncryptedR<T> result = new EncryptedR<>();
result.code = 200;
result.msg = "success";

try {
if (data instanceof Boolean) {
result.data = encryptBoolean((Boolean) data);
} else {
String jsonData = new ObjectMapper().writeValueAsString(data);
result.data = encrypt(jsonData);
}
} catch (JsonProcessingException e) {
throw new RuntimeException("序列化数据失败", e);
}

return result;
}
}

2. 前端页面案例

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>AES CBC 解密测试</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    input, button {
      padding: 8px;
      margin-top: 10px;
      width: 100%;
      max-width: 400px;
      box-sizing: border-box;
    }
    #result {
      margin-top: 20px;
      white-space: pre-wrap;
      color: green;
    }
    #error {
      margin-top: 20px;
      color: red;
    }
  </style>
</head>
<body>

<h2>前端 AES CBC 解密测试页面</h2>

<label for="encrypted">请输入加密数据 (Base64):</label><br/>
<input type="text" id="encrypted" value="eYIQuRPS54iO2+qLft7GVQ==" />

<br/>
<button onclick="decrypt()">解密</button>

<div id="result"></div>
<div id="error"></div>

<script>
const SECRET_KEY = "rK7CLfWzZqFmYlJQ6oVpXQ=="; // 必须是16字节
const IV = "aB3#cD9!xY7@kL2$";         // 必须是16字节

function decrypt() {
  const encrypted = document.getElementById("encrypted").value.trim();
  const resultDiv = document.getElementById("result");
  const errorDiv = document.getElementById("error");

  resultDiv.textContent = "";
  errorDiv.textContent = "";

  if (!encrypted) {
    errorDiv.textContent = "请输入加密字符串";
    return;
  }

  try {
    const key = CryptoJS.enc.Utf8.parse(SECRET_KEY);
    const iv = CryptoJS.enc.Utf8.parse(IV);

    const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });

    const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
    if (!decryptedText) {
      throw new Error("解密结果为空,请检查密钥、IV或加密数据是否正确");
    }

    resultDiv.textContent = "解密成功:" + decryptedText;

  } catch (e) {
    errorDiv.textContent = "解密失败:" + e.message;
  }
}
</script>

</body>
</html>

3.接口

 4.需要注意的是我使用的是AES-128 加密算法,密钥长度必须是16 字节(128 位)

 为什么 IV 也必须是 16 字节?
AES 是一种 分组加密算法(Block Cipher)
使用 CBC 模式时,每个明文块会与前一个密文块进行异或操作
第一个块没有前一块,所以需要一个初始化向量(IV)来替代
AES 的块大小固定为 128 位(16 字节)
所以:IV 必须等于块大小,也就是 16 字节

 

 

posted @ 2025-05-13 17:15  Fyy发大财  阅读(147)  评论(0)    收藏  举报