共383行

前后端加解密demo

2026-03-20 11:16:49

我会为你实现一个完整的 Demo,采用「非对称加密交换对称密钥 + 对称加密传输数据」的工业级方案(和 HTTPS 核心逻辑一致),既保证安全性,又兼顾性能。

整体流程设计

  1. 前端(JS)
  2. 后端(Java)
  3. 前端:用 AES 密钥解密后端返回的结果
┌──────────┐        ┌──────────┐
│  前端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)已满足需求,无需额外依赖。


一、后端(Java)代码

1. RSA 密钥工具类(生成/加载密钥对)

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());
    }
}

2. AES 加密工具类

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);
    }
}

3. Spring Boot 接口(核心业务逻辑)

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;
    }
}

二、前端(JS)代码

<!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>

三、关键说明

1. 运行前提

2. 核心细节解释

3. 实际项目优化建议


总结

  1. 整个流程采用「RSA 加密 AES 密钥 + AES 加密业务数据」的经典方案,兼顾安全性和性能;
  2. 前端负责生成 AES 密钥,用后端公钥加密后传输,后端用私钥解密出 AES 密钥,完成数据加解密;
  3. 核心关键点:前后端 AES 模式/填充、RSA 填充、编码格式必须完全一致,否则会解密失败。