共383行
2026-03-20 11:16:49
我会为你实现一个完整的 Demo,采用「非对称加密交换对称密钥 + 对称加密传输数据」的工业级方案(和 HTTPS 核心逻辑一致),既保证安全性,又兼顾性能。
┌──────────┐ ┌──────────┐
│ 前端JS │ │ 后端Java │
└────┬─────┘ └─────┬────┘
│ │
│ 1. 获取 RSA 公钥 │
│ ──────────────────> │
│ │
│ 返回公钥 │
│ <────────────────── │
│ │
│ 2. 前端本地生成: │
│ AES Key + IV │
│ │
│ 3. 用 RSA 公钥加密 │
│ AES Key │
│ │
│ 4. 用 AES 加密业务数据 │
│ │
│ 5. 传给后端: │
│ encryptedAesKey │
│ encryptedData │
│ iv │
│ ──────────────────> │
│ │
│ 6. 用 RSA 私钥
│ 解密出 AES Key
│ │
│ 7. 用 AES Key
│ 解密业务数据
│ │
│ 8. 后端处理业务逻辑
│ │
│ 9. 用同一个 AES Key
│ 加密返回结果
│ │
│ 返回 encryptedResult │
│ <────────────────── │
│ │
│ 10. 用 AES Key 解密 │
│ 后端返回结果 │
│ │
需要安装 crypto-js(AES 加密)和 jsencrypt(RSA 加密):
npm install crypto-js jsencrypt
# 或直接引入 CDN
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@3.3.2/bin/jsencrypt.min.js"></script>Java 内置加密库(JCE)已满足需求,无需额外依赖。
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA 密钥工具类(生成公钥/私钥,加密/解密)
*/
public class RSAUtil {
// 密钥算法
private static final String ALGORITHM = "RSA";
// 密钥长度(2048 位足够安全)
private static final int KEY_SIZE = 2048;
// 生成 RSA 密钥对(公钥+私钥)
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
// 公钥加密
public static String encryptByPublicKey(String data, String publicKeyStr) throws Exception {
// 解码公钥
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// 加密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 私钥解密
public static String decryptByPrivateKey(String encryptedData, String privateKeyStr) throws Exception {
// 解码私钥
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 解密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
// 获取公钥字符串(Base64 编码)
public static String getPublicKeyStr(KeyPair keyPair) {
return Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
}
// 获取私钥字符串(Base64 编码)
public static String getPrivateKeyStr(KeyPair keyPair) {
return Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
}
}import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* AES 加密工具类(CBC 模式,带 IV,更安全)
*/
public class AESUtil {
// 算法/模式/填充
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
// 密钥长度(128 位,AES-256 需要额外配置 JCE)
private static final int KEY_SIZE = 128;
// AES 加密(key:16 位字符串,iv:16 位字符串)
public static String encrypt(String data, String key, String iv) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// AES 解密
public static String decrypt(String encryptedData, String key, String iv) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}import org.springframework.web.bind.annotation.*;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api")
public class EncryptController {
// 预生成 RSA 密钥对(实际项目中应配置在配置文件,而非硬编码)
private static final KeyPair RSA_KEY_PAIR;
private static final String RSA_PUBLIC_KEY;
private static final String RSA_PRIVATE_KEY;
static {
try {
RSA_KEY_PAIR = RSAUtil.generateKeyPair();
RSA_PUBLIC_KEY = RSAUtil.getPublicKeyStr(RSA_KEY_PAIR);
RSA_PRIVATE_KEY = RSAUtil.getPrivateKeyStr(RSA_KEY_PAIR);
} catch (Exception e) {
throw new RuntimeException("生成 RSA 密钥对失败", e);
}
}
// 1. 获取 RSA 公钥(前端用来加密 AES 密钥)
@GetMapping("/public-key")
public Map<String, String> getPublicKey() {
Map<String, String> result = new HashMap<>();
result.put("publicKey", RSA_PUBLIC_KEY);
return result;
}
// 2. 接收前端加密数据,解密后处理,再加密返回
@PostMapping("/encrypt-data")
public Map<String, String> handleEncryptData(@RequestBody Map<String, String> request) throws Exception {
// 1. 获取前端传的参数
String encryptedAesKey = request.get("encryptedAesKey"); // RSA 加密后的 AES 密钥
String encryptedData = request.get("encryptedData"); // AES 加密后的业务数据
String iv = request.get("iv"); // AES 的 IV(初始化向量)
// 2. 用 RSA 私钥解密 AES 密钥
String aesKey = RSAUtil.decryptByPrivateKey(encryptedAesKey, RSA_PRIVATE_KEY);
System.out.println("解密后的 AES 密钥:" + aesKey);
// 3. 用 AES 密钥解密业务数据
String originalData = AESUtil.decrypt(encryptedData, aesKey, iv);
System.out.println("解密后的业务数据:" + originalData);
// 4. 处理业务数据(示例:拼接字符串)
String processedData = "后端处理结果:" + originalData + " - " + UUID.randomUUID();
// 5. 用 AES 密钥加密处理后的结果
String encryptedResult = AESUtil.encrypt(processedData, aesKey, iv);
// 6. 返回加密后的结果
Map<String, String> response = new HashMap<>();
response.put("encryptedResult", encryptedResult);
return response;
}
}<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>加密通信 Demo</title>
<!-- 引入加密库 -->
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@3.3.2/bin/jsencrypt.min.js"></script>
</head>
<body>
<button onclick="sendEncryptedData()">发送加密数据</button>
<div id="result"></div>
<script>
// 生成 16 位随机字符串(AES 密钥/IV 要求 16 位)
function generateRandomString(length = 16) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// 核心逻辑:发送加密数据
async function sendEncryptedData() {
try {
// 1. 获取后端的 RSA 公钥
const publicKeyRes = await fetch('/api/public-key');
const publicKeyData = await publicKeyRes.json();
const publicKey = publicKeyData.publicKey;
// 2. 生成 AES 密钥和 IV(初始化向量)
const aesKey = generateRandomString(16); // AES 密钥(16 位)
const iv = generateRandomString(16); // AES IV(16 位)
console.log("生成的 AES 密钥:", aesKey);
console.log("生成的 AES IV:", iv);
// 3. 用 RSA 公钥加密 AES 密钥
const rsaEncryptor = new JSEncrypt();
rsaEncryptor.setPublicKey(publicKey);
const encryptedAesKey = rsaEncryptor.encrypt(aesKey);
console.log("RSA 加密后的 AES 密钥:", encryptedAesKey);
// 4. 要发送的原始业务数据
const originalData = "Hello, 加密通信!";
console.log("原始业务数据:", originalData);
// 5. 用 AES 加密业务数据(CBC 模式,和后端一致)
const encryptedData = CryptoJS.AES.encrypt(
originalData,
CryptoJS.enc.Utf8.parse(aesKey),
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
).toString();
console.log("AES 加密后的业务数据:", encryptedData);
// 6. 发送数据到后端
const response = await fetch('/api/encrypt-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
encryptedAesKey: encryptedAesKey,
encryptedData: encryptedData,
iv: iv
})
});
// 7. 接收后端加密结果并解密
const responseData = await response.json();
const encryptedResult = responseData.encryptedResult;
console.log("后端返回的加密结果:", encryptedResult);
// 8. 用 AES 密钥解密后端结果
const decryptedResult = CryptoJS.AES.decrypt(
encryptedResult,
CryptoJS.enc.Utf8.parse(aesKey),
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
).toString(CryptoJS.enc.Utf8);
console.log("解密后的后端结果:", decryptedResult);
// 显示结果到页面
document.getElementById('result').innerText = "最终结果:" + decryptedResult;
} catch (e) {
console.error("加密通信失败:", e);
document.getElementById('result').innerText = "失败:" + e.message;
}
}
</script>
</body>
</html>