Skip to content

数据加解密

RSA 加解密

RSA 加密算法是一种非对称加密算法,即 RSA 拥有一对密钥(公钥 和 私钥),公钥可公开。公钥加密的数据,只能由私钥解密;私钥加密的数据只能由公钥解密。

用生活例子理解 RSA 想象一下邮箱和钥匙的例子:

传统方式(对称加密):
你和朋友共用一个邮箱,你们俩都有同一把钥匙
问题:如何安全地把钥匙给朋友?

RSA方式(非对称加密):
你有一个特殊的邮箱:
- 投递口(公钥):任何人都可以往里投信
- 取信钥匙(私钥):只有你有,只有你能取信

流程:
1. 你把邮箱地址告诉所有人(公钥公开)
2. 朋友用投递口投信(用公钥加密)
3. 只有你能用钥匙取信(用私钥解密)

RSA 的核心特点

公钥(Public Key):
- 可以公开给任何人
- 用来加密数据
- 就像邮箱的投递口

私钥(Private Key):
- 绝对保密,只有自己知道
- 用来解密数据
- 就像邮箱的钥匙

神奇之处:
公钥加密的数据,只有对应的私钥才能解密!

RSA 的优缺点

✅ 优点

  1. 安全性极高:基于数学难题,"大数分解"
  2. 密钥分发简单:公钥可以公开传输
  3. 支持数字签名:可以验证身份

❌ 缺点

  1. 速度慢:比 AES 慢 100-1000 倍
  2. 数据大小限制:只能加密小数据(通常<245 字节)
  3. 计算复杂:需要大量 CPU 资源
  4. 密钥长度大:通常需要 2048 位以上

登录加解密流程

RSA 工具类

java
package com.example.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA加解密工具类
 *
 * 这是整个RSA加解密的核心工具类
 * 包含了RSA密钥对生成、加密、解密的所有核心方法
 *
 * @author YourName
 */
@Slf4j
public class RSAUtils {

    /**
     * RSA算法名称
     */
    private static final String ALGORITHM = "RSA";

    /**
     * RSA密钥长度,2048位是目前推荐的安全长度
     * 1024位已经不够安全,4096位太慢,2048位是最佳选择
     */
    private static final int KEY_SIZE = 2048;

    /**
     * RSA加密填充方式
     * PKCS1Padding是最常用的填充方式
     */
    private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";

    /**
     * 生成RSA密钥对
     *
     * 这个方法会生成一对RSA密钥:公钥和私钥
     * 公钥用于加密,私钥用于解密
     *
     * @return KeyPair 包含公钥和私钥的密钥对
     * @throws Exception 生成失败时抛出异常
     */
    public static KeyPair generateKeyPair() throws Exception {
        log.info("🔑 开始生成RSA密钥对,密钥长度: {} 位", KEY_SIZE);

        try {
            // 1. 获取RSA算法的密钥对生成器
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);

            // 2. 设置密钥长度
            keyPairGenerator.initialize(KEY_SIZE);

            // 3. 生成密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();

            log.info("✅ RSA密钥对生成成功");
            return keyPair;

        } catch (Exception e) {
            log.error("❌ RSA密钥对生成失败: {}", e.getMessage(), e);
            throw new Exception("RSA密钥对生成失败", e);
        }
    }

    /**
     * 将公钥转换为字符串格式
     *
     * 公钥需要传给前端,所以要转换为字符串格式
     * 使用PEM格式,这是业界标准格式
     *
     * @param publicKey 公钥对象
     * @return String PEM格式的公钥字符串
     */
    public static String publicKeyToString(PublicKey publicKey) {
        log.debug("🔄 开始转换公钥为字符串格式");

        try {
            // 1. 获取公钥的字节数组
            byte[] publicKeyBytes = publicKey.getEncoded();

            // 2. 转换为Base64字符串
            String publicKeyBase64 = Base64.encodeBase64String(publicKeyBytes);

            // 3. 构建PEM格式的公钥字符串
            StringBuilder pemFormat = new StringBuilder();
            pemFormat.append("-----BEGIN PUBLIC KEY-----\n");

            // 4. 每64个字符换一行(PEM格式标准)
            for (int i = 0; i < publicKeyBase64.length(); i += 64) {
                int endIndex = Math.min(i + 64, publicKeyBase64.length());
                pemFormat.append(publicKeyBase64, i, endIndex).append("\n");
            }

            pemFormat.append("-----END PUBLIC KEY-----");

            String result = pemFormat.toString();
            log.debug("✅ 公钥转换完成,长度: {} 字符", result.length());

            return result;

        } catch (Exception e) {
            log.error("❌ 公钥转换失败: {}", e.getMessage(), e);
            throw new RuntimeException("公钥转换失败", e);
        }
    }

    /**
     * 将私钥转换为字符串格式
     *
     * 注意:私钥字符串只在服务器内部使用,绝对不能暴露给外部
     *
     * @param privateKey 私钥对象
     * @return String PEM格式的私钥字符串
     */
    public static String privateKeyToString(PrivateKey privateKey) {
        log.debug("🔄 开始转换私钥为字符串格式");

        try {
            byte[] privateKeyBytes = privateKey.getEncoded();
            String privateKeyBase64 = Base64.encodeBase64String(privateKeyBytes);

            StringBuilder pemFormat = new StringBuilder();
            pemFormat.append("-----BEGIN PRIVATE KEY-----\n");

            for (int i = 0; i < privateKeyBase64.length(); i += 64) {
                int endIndex = Math.min(i + 64, privateKeyBase64.length());
                pemFormat.append(privateKeyBase64, i, endIndex).append("\n");
            }

            pemFormat.append("-----END PRIVATE KEY-----");

            String result = pemFormat.toString();
            log.debug("✅ 私钥转换完成,长度: {} 字符", result.length());

            return result;

        } catch (Exception e) {
            log.error("❌ 私钥转换失败: {}", e.getMessage(), e);
            throw new RuntimeException("私钥转换失败", e);
        }
    }

    /**
     * 从字符串还原公钥对象
     *
     * 用于从PEM格式的字符串还原公钥对象
     * 主要用于测试和某些特殊场景
     *
     * @param publicKeyString PEM格式的公钥字符串
     * @return PublicKey 公钥对象
     * @throws Exception 还原失败时抛出异常
     */
    public static PublicKey stringToPublicKey(String publicKeyString) throws Exception {
        log.debug("🔄 开始从字符串还原公钥对象");

        try {
            // 1. 移除PEM格式的头尾标识和换行符
            String publicKeyPEM = publicKeyString
                    .replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s", "");

            // 2. 解码Base64字符串
            byte[] publicKeyBytes = Base64.decodeBase64(publicKeyPEM);

            // 3. 创建公钥规格对象
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);

            // 4. 获取RSA密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

            // 5. 生成公钥对象
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            log.debug("✅ 公钥对象还原成功");
            return publicKey;

        } catch (Exception e) {
            log.error("❌ 公钥对象还原失败: {}", e.getMessage(), e);
            throw new Exception("公钥对象还原失败", e);
        }
    }

    /**
     * 从字符串还原私钥对象
     *
     * @param privateKeyString PEM格式的私钥字符串
     * @return PrivateKey 私钥对象
     * @throws Exception 还原失败时抛出异常
     */
    public static PrivateKey stringToPrivateKey(String privateKeyString) throws Exception {
        log.debug("🔄 开始从字符串还原私钥对象");

        try {
            String privateKeyPEM = privateKeyString
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s", "");

            byte[] privateKeyBytes = Base64.decodeBase64(privateKeyPEM);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            log.debug("✅ 私钥对象还原成功");
            return privateKey;

        } catch (Exception e) {
            log.error("❌ 私钥对象还原失败: {}", e.getMessage(), e);
            throw new Exception("私钥对象还原失败", e);
        }
    }

    /**
     * 使用公钥加密数据
     *
     * 这是前端会用到的加密过程的后端等价实现
     * 主要用于测试和验证
     *
     * @param data 要加密的原始数据
     * @param publicKey 公钥对象
     * @return String Base64编码的加密结果
     * @throws Exception 加密失败时抛出异常
     */
    public static String encryptByPublicKey(String data, PublicKey publicKey) throws Exception {
        log.debug("🔒 开始使用公钥加密数据,数据长度: {} 字符", data.length());

        try {
            // 1. 创建加密器
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            // 2. 用公钥初始化加密器
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            // 3. 将字符串转换为字节数组
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);

            // 4. 执行加密
            byte[] encryptedBytes = cipher.doFinal(dataBytes);

            // 5. 转换为Base64字符串
            String encryptedData = Base64.encodeBase64String(encryptedBytes);

            log.debug("✅ 公钥加密成功,加密后长度: {} 字符", encryptedData.length());
            return encryptedData;

        } catch (Exception e) {
            log.error("❌ 公钥加密失败: {}", e.getMessage(), e);
            throw new Exception("公钥加密失败", e);
        }
    }

    /**
     * 使用私钥解密数据
     *
     * 这是后端的核心解密方法
     * 用于解密前端发送的加密数据
     *
     * @param encryptedData Base64编码的加密数据
     * @param privateKey 私钥对象
     * @return String 解密后的原始数据
     * @throws Exception 解密失败时抛出异常
     */
    public static String decryptByPrivateKey(String encryptedData, PrivateKey privateKey) throws Exception {
        log.debug("🔓 开始使用私钥解密数据,加密数据长度: {} 字符", encryptedData.length());

        try {
            // 1. 创建解密器
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            // 2. 用私钥初始化解密器
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            // 3. 将Base64字符串解码为字节数组
            byte[] encryptedBytes = Base64.decodeBase64(encryptedData);

            // 4. 执行解密
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);

            // 5. 转换为字符串
            String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8);

            log.debug("✅ 私钥解密成功,解密后长度: {} 字符", decryptedData.length());
            return decryptedData;

        } catch (Exception e) {
            log.error("❌ 私钥解密失败: {}", e.getMessage(), e);
            throw new Exception("私钥解密失败", e);
        }
    }

    /**
     * 验证RSA密钥对是否匹配
     *
     * 通过加密-解密测试来验证公钥和私钥是否为一对
     *
     * @param publicKey 公钥
     * @param privateKey 私钥
     * @return boolean true表示匹配,false表示不匹配
     */
    public static boolean verifyKeyPair(PublicKey publicKey, PrivateKey privateKey) {
        log.debug("🔍 开始验证RSA密钥对匹配性");

        try {
            // 测试数据
            String testData = "RSA KeyPair Test: " + System.currentTimeMillis();

            // 用公钥加密
            String encrypted = encryptByPublicKey(testData, publicKey);

            // 用私钥解密
            String decrypted = decryptByPrivateKey(encrypted, privateKey);

            // 比较原文和解密结果
            boolean isMatch = testData.equals(decrypted);

            if (isMatch) {
                log.debug("✅ RSA密钥对验证成功:公钥和私钥匹配");
            } else {
                log.warn("❌ RSA密钥对验证失败:公钥和私钥不匹配");
            }

            return isMatch;

        } catch (Exception e) {
            log.error("❌ RSA密钥对验证异常: {}", e.getMessage(), e);
            return false;
        }
    }
}

RSA 密钥管理服务

java
package com.demo.zzy.service;

import com.demo.zzy.utils.RSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.UUID;

/**
 * @author zzy
 * @description: TODO
 * @date 2025/7/1
 */
/**
 * RSA密钥管理服务
 *
 * 这个服务负责:
 * 1. 生成和管理RSA密钥对
 * 2. 提供公钥给前端
 * 3. 使用私钥解密前端数据
 * 4. 定期更换密钥(可选)
 */
@Service
@Slf4j
public class RSAKeyService {
    /**
     * 当前使用的RSA密钥对
     */
    private KeyPair currentKeyPair;

    /**
     * 当前密钥的唯一标识ID
     * 用于标识不同批次的密钥,方便管理和调试
     */
    private String currentKeyId;

    /**
     * 服务启动时自动初始化密钥对
     *
     * @PostConstruct 注解确保在Spring容器创建Bean后立即执行
     */
    @PostConstruct
    public void initializeKeys() {
        log.info("🚀 RSA密钥服务开始初始化...");

        try {
            // 生成新的密钥对
            generateNewKeyPair();

            // 验证密钥对是否正常
            if (verifyCurrentKeyPair()) {
                log.info("🎉 RSA密钥服务初始化成功!");
                log.info("📊 密钥信息:");
                log.info("   - 密钥ID: {}", currentKeyId);
                log.info("   - 算法: RSA");
                log.info("   - 密钥长度: 2048位");
                log.info("   - 公钥长度: {} 字符", getPublicKeyString().length());
            } else {
                throw new RuntimeException("密钥对验证失败");
            }

        } catch (Exception e) {
            log.error("❌ RSA密钥服务初始化失败: {}", e.getMessage(), e);
            throw new RuntimeException("RSA密钥服务初始化失败", e);
        }
    }

    /**
     * 生成新的RSA密钥对
     *
     * @throws Exception 生成失败时抛出异常
     */
    private void generateNewKeyPair() throws Exception {
        log.info("🔑 开始生成新的RSA密钥对...");

        // 1. 生成密钥对
        this.currentKeyPair = RSAUtils.generateKeyPair();

        // 2. 生成新的密钥ID
        this.currentKeyId = "RSA-" + UUID.randomUUID().toString();

        log.info("✅ 新密钥对生成完成,密钥ID: {}", currentKeyId);
    }

    /**
     * 验证当前密钥对是否正常
     *
     * @return boolean 验证结果
     */
    private boolean verifyCurrentKeyPair() {
        if (currentKeyPair == null) {
            log.error("❌ 密钥对为空,验证失败");
            return false;
        }

        log.info("🔍 开始验证密钥对...");

        try {
            PublicKey publicKey = currentKeyPair.getPublic();
            PrivateKey privateKey = currentKeyPair.getPrivate();

            // 使用RSAUtils的验证方法
            boolean isValid = RSAUtils.verifyKeyPair(publicKey, privateKey);

            if (isValid) {
                log.info("✅ 密钥对验证成功");
            } else {
                log.error("❌ 密钥对验证失败");
            }

            return isValid;

        } catch (Exception e) {
            log.error("❌ 密钥对验证异常: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取当前公钥的字符串格式
     *
     * 这个方法会被Controller调用,返回给前端使用
     *
     * @return String PEM格式的公钥字符串
     */
    public String getPublicKeyString() {
        if (currentKeyPair == null) {
            throw new RuntimeException("密钥对未初始化");
        }

        try {
            PublicKey publicKey = currentKeyPair.getPublic();
            String publicKeyString = RSAUtils.publicKeyToString(publicKey);

            log.debug("📤 提供公钥字符串,长度: {} 字符", publicKeyString.length());
            return publicKeyString;

        } catch (Exception e) {
            log.error("❌ 获取公钥字符串失败: {}", e.getMessage(), e);
            throw new RuntimeException("获取公钥字符串失败", e);
        }
    }

    /**
     * 获取当前密钥ID
     *
     * @return String 密钥ID
     */
    public String getCurrentKeyId() {
        return currentKeyId;
    }

    /**
     * 使用当前私钥解密数据
     *
     * 这是最核心的方法,用于解密前端发送的加密数据
     *
     * @param encryptedData Base64编码的加密数据
     * @return String 解密后的原始数据
     * @throws Exception 解密失败时抛出异常
     */
    public String decryptData(String encryptedData) throws Exception {
        if (currentKeyPair == null) {
            throw new RuntimeException("密钥对未初始化");
        }

        log.debug("🔓 开始解密数据...");
        log.debug("📥 接收到加密数据,长度: {} 字符", encryptedData.length());

        try {
            PrivateKey privateKey = currentKeyPair.getPrivate();
            String decryptedData = RSAUtils.decryptByPrivateKey(encryptedData, privateKey);

            log.debug("✅ 数据解密成功");
            log.debug("📤 解密结果长度: {} 字符", decryptedData.length());

            return decryptedData;

        } catch (Exception e) {
            log.error("❌ 数据解密失败: {}", e.getMessage(), e);
            throw new Exception("数据解密失败: " + e.getMessage(), e);
        }
    }

    /**
     * 使用当前公钥加密数据
     *
     * 主要用于测试目的,模拟前端加密过程
     *
     * @param data 要加密的原始数据
     * @return String Base64编码的加密结果
     * @throws Exception 加密失败时抛出异常
     */
    public String encryptData(String data) throws Exception {
        if (currentKeyPair == null) {
            throw new RuntimeException("密钥对未初始化");
        }

        log.debug("🔒 开始加密数据...");
        log.debug("📥 原始数据长度: {} 字符", data.length());

        try {
            PublicKey publicKey = currentKeyPair.getPublic();
            String encryptedData = RSAUtils.encryptByPublicKey(data, publicKey);

            log.debug("✅ 数据加密成功");
            log.debug("📤 加密结果长度: {} 字符", encryptedData.length());

            return encryptedData;

        } catch (Exception e) {
            log.error("❌ 数据加密失败: {}", e.getMessage(), e);
            throw new Exception("数据加密失败: " + e.getMessage(), e);
        }
    }

    /**
     * 手动更换密钥对
     *
     * 可以在运行时调用此方法更换密钥,提高安全性
     *
     * @return String 新的密钥ID
     * @throws Exception 更换失败时抛出异常
     */
    public String rotateKeys() throws Exception {
        log.info("🔄 开始手动更换RSA密钥对...");

        String oldKeyId = currentKeyId;

        try {
            // 生成新密钥对
            generateNewKeyPair();

            // 验证新密钥对
            if (!verifyCurrentKeyPair()) {
                throw new Exception("新密钥对验证失败");
            }

            log.info("✅ RSA密钥对更换成功");
            log.info("🔄 旧密钥ID: {} → 新密钥ID: {}", oldKeyId, currentKeyId);

            return currentKeyId;

        } catch (Exception e) {
            log.error("❌ RSA密钥对更换失败: {}", e.getMessage(), e);
            throw new Exception("RSA密钥对更换失败", e);
        }
    }

    /**
     * 执行RSA加解密完整测试
     *
     * 用于验证整个RSA加解密流程是否正常
     *
     * @return boolean 测试结果
     */
    public boolean performFullTest() {
        log.info("🧪 开始执行RSA完整功能测试...");

        try {
            // 测试数据
            String originalData = "RSA Full Test: Hello World! 测试中文 " + System.currentTimeMillis();
            log.info("📝 测试数据: {}", originalData);

            // 1. 加密测试
            String encryptedData = encryptData(originalData);
            log.info("🔒 加密结果: {}...", encryptedData.substring(0, Math.min(50, encryptedData.length())));

            // 2. 解密测试
            String decryptedData = decryptData(encryptedData);
            log.info("🔓 解密结果: {}", decryptedData);

            // 3. 验证结果
            boolean testPassed = originalData.equals(decryptedData);

            if (testPassed) {
                log.info("🎉 RSA完整功能测试通过!");
            } else {
                log.error("❌ RSA完整功能测试失败!原文和解密结果不匹配");
            }

            return testPassed;

        } catch (Exception e) {
            log.error("❌ RSA完整功能测试异常: {}", e.getMessage(), e);
            return false;
        }
    }

}

RSA 控制器

java
package com.demo.zzy.controller.pure;

import com.demo.zzy.pojo.dto.PublicKeyResponse;
import com.demo.zzy.service.RSAKeyService;
import com.demo.zzy.utils.ResponseData;
import kotlin.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zzy
 * @description: TODO
 * @date 2025/7/1
 */
@RestController
@RequestMapping("/rsa")
@Slf4j
public class RSAController {

    @Autowired
    private RSAKeyService rsaKeyService;

    /**
     * 获取RSA公钥
     * <p>
     * 前端调用此接口获取公钥,用于加密敏感数据
     * <p>
     * GET /api/rsa/public-key
     *
     * @return Result<PublicKeyResponse> 包含公钥信息的响应
     */
    @GetMapping("/public-key")
    public ResponseData<?> getPublicKey() {
        log.info("🔑 收到获取公钥请求");

        try {
            // 1. 获取公钥字符串
            String publicKeyString = rsaKeyService.getPublicKeyString();

            // 2. 获取密钥ID
            String keyId = rsaKeyService.getCurrentKeyId();

            // 3. 构建响应对象
            PublicKeyResponse response = PublicKeyResponse.builder()
                    .publicKey(publicKeyString)
                    .keyId(keyId)
                    .timestamp(System.currentTimeMillis())
                    .algorithm("RSA")
                    .keySize(2048)
                    .build();

            log.info("✅ 公钥获取成功,密钥ID: {}", keyId);
            log.debug("📏 公钥长度: {} 字符", publicKeyString.length());

            return ResponseData.success(response);

        } catch (Exception e) {
            log.error("❌ 获取公钥失败: {}", e.getMessage(), e);
            return ResponseData.error("获取公钥失败: " + e.getMessage());
        }
    }

    /**
     * 手动更换RSA密钥
     * <p>
     * 触发密钥轮换,生成新的密钥对
     * <p>
     * POST /api/rsa/rotate-keys
     *
     * @return Result<Map < String, Object>> 更换结果
     */
    @PostMapping("/rotate-keys")
    public ResponseData<?> rotateKeys() {
        log.info("🔄 收到密钥轮换请求");

        try {
            String oldKeyId = rsaKeyService.getCurrentKeyId();
            String newKeyId = rsaKeyService.rotateKeys();

            Map<String, Object> response = new HashMap<>();
            response.put("oldKeyId", oldKeyId);
            response.put("newKeyId", newKeyId);
            response.put("timestamp", System.currentTimeMillis());
            response.put("message", "密钥轮换成功");

            log.info("✅ 密钥轮换成功: {} → {}", oldKeyId, newKeyId);
            return ResponseData.success(response);

        } catch (Exception e) {
            log.error("❌ 密钥轮换失败: {}", e.getMessage(), e);
            return ResponseData.error("密钥轮换失败: " + e.getMessage());
        }
    }

}

认证控制器(RSA 登录示例)

java
package com.demo.zzy.controller.pure;

import com.demo.zzy.pojo.dto.LoginRequest;
import com.demo.zzy.service.RSAKeyService;
import com.demo.zzy.utils.ResponseData;
import kotlin.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zzy
 * @description: TODO
 * @date 2025/7/1
 */
@RestController
@RequestMapping("/auth")
@Slf4j
public class AuthController {
    @Autowired
    private RSAKeyService rsaKeyService;

    /**
     * RSA加密登录
     * <p>
     * 接收前端发送的用户名和RSA加密后的密码
     *
     * @param request 登录请求(密码已加密)
     * @return Result<Map < String, Object>> 登录结果
     */
    @PostMapping("/login")
    public ResponseData<?> login(@Valid @RequestBody LoginRequest request) {
        log.info("🔐 收到加密登录请求");
        log.info("👤 用户名: {}", request.getUsername());
        log.info("🔒 加密密码长度: {} 字符", request.getPassword().length());
        log.info("⏰ 请求时间戳: {}", request.getTimestamp());

        try {
            // 1. 验证时间戳(防重放攻击)
            long currentTime = System.currentTimeMillis();
            long timeDiff = Math.abs(currentTime - request.getTimestamp());

            if (timeDiff > 5 * 60 * 1000) { // 5分钟有效期
                log.warn("⚠️ 请求时间戳无效,时间差: {}ms", timeDiff);
                return ResponseData.error("请求已过期,请重新登录");
            }

            // 2. 使用RSA私钥解密密码
            log.info("🔓 开始解密用户密码...");
            String plainPassword = rsaKeyService.decryptData(request.getPassword());
            log.info("✅ 密码解密成功");
            log.debug("🔓 解密后密码长度: {} 字符", plainPassword.length());

            // 3. 验证用户名和密码(这里用简单的示例逻辑)
            boolean loginSuccess = validateUser(request.getUsername(), plainPassword);

            if (loginSuccess) {
                // 4. 登录成功,生成响应
                Map<String, Object> response = new HashMap<>();
                response.put("success", true);
                response.put("username", request.getUsername());
                response.put("token", "mock-jwt-token-" + System.currentTimeMillis());
                response.put("message", "登录成功");
                response.put("loginTime", System.currentTimeMillis());

                log.info("🎉 用户登录成功: {}", request.getUsername());
                return ResponseData.success(response);

            } else {
                log.warn("❌ 用户登录失败: {}", request.getUsername());
                return ResponseData.error("用户名或密码错误");
            }

        } catch (Exception e) {
            log.error("❌ 登录处理异常: {}", e.getMessage(), e);
            return ResponseData.error("登录失败: " + e.getMessage());
        } finally {
            // 5. 安全清理:清空可能包含明文密码的变量
            request.setPassword(null);
        }
    }

    /**
     * 简单的用户验证逻辑(示例)
     * 实际项目中应该连接数据库验证
     */
    private boolean validateUser(String username, String password) {
        log.info("🔍 验证用户: {}", username);

        // 示例:简单的用户验证
        if ("admin".equals(username) && "123456".equals(password)) {
            return true;
        }
        if ("user".equals(username) && "password".equals(password)) {
            return true;
        }
        if ("test".equals(username) && "test123".equals(password)) {
            return true;
        }

        return false;
    }

}

使用场景:

  • 🔐 用户登录密码加密传输
  • 📧 敏感信息加密存储
  • 🔑 API 密钥安全传输
  • 💳 支付信息加密处理

为什么要在服务启动时自动初始化 RSA 密钥?

RSA 密钥生成耗时较长

测试时间:

RSA-1024位: ~50-100ms
RSA-2048位: ~200-500ms  ← 我们用的
RSA-4096位: ~1-3秒

用户体验:

用户A (第一个请求): 等待500ms ❌ 体验差
用户B (后续请求):   立即响应   ✅ 体验好

前端 创建 RSA 工具类

RSAUtils
ts
// src/utils/rsa.ts
import JSEncrypt from "jsencrypt";
import { ElMessage } from "element-plus";

/**
 * RSA加密工具类
 * 统一管理RSA公钥获取和数据加密
 */
export class RSAUtils {
  // 单例模式,全局只有一个实例
  private static instance: RSAUtils | null = null;

  // 存储公钥信息
  private publicKey: string | null = null;
  private keyId: string | null = null;
  private lastUpdateTime: number = 0;

  // 配置参数
  private readonly API_BASE =
    import.meta.env.VITE_API_BASE_URL || "http://localhost:8080";
  private readonly KEY_CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存

  /**
   * 获取RSA工具实例(单例模式)
   */
  public static getInstance(): RSAUtils {
    if (!RSAUtils.instance) {
      RSAUtils.instance = new RSAUtils();
    }
    return RSAUtils.instance;
  }

  /**
   * 私有构造函数,防止外部直接创建实例
   */
  private constructor() {
    console.log("🔐 RSAUtils实例已创建");
  }

  /**
   * 获取RSA公钥
   * 带缓存机制,避免频繁请求
   */
  private async getPublicKey(): Promise<string> {
    console.log("🔑 开始获取RSA公钥...");

    // 检查缓存是否有效
    const now = Date.now();
    if (this.publicKey && now - this.lastUpdateTime < this.KEY_CACHE_DURATION) {
      console.log("✅ 使用缓存的公钥,密钥ID:", this.keyId);
      return this.publicKey;
    }

    try {
      console.log("🌐 从服务器获取新的公钥...");

      const response = await fetch(`${this.API_BASE}/api/rsa/public-key`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      });

      if (!response.ok) {
        throw new Error(`HTTP错误: ${response.status}`);
      }

      const result = await response.json();

      if (result.code !== 200) {
        throw new Error(result.message || "获取公钥失败");
      }

      // 更新缓存
      this.publicKey = result.data.publicKey;
      this.keyId = result.data.keyId;
      this.lastUpdateTime = now;

      console.log("✅ 公钥获取成功:");
      console.log("   - 密钥ID:", this.keyId);
      console.log("   - 算法:", result.data.algorithm);
      console.log("   - 密钥长度:", result.data.keySize, "位");

      return this.publicKey;
    } catch (error: any) {
      console.error("❌ 获取公钥失败:", error);
      throw new Error(`获取公钥失败: ${error.message}`);
    }
  }

  /**
   * 使用RSA公钥加密数据
   * @param data 要加密的数据
   * @returns Promise<string> 加密后的Base64字符串
   */
  public async encrypt(data: string): Promise<string> {
    if (!data) {
      throw new Error("加密数据不能为空");
    }

    console.log("🔒 开始RSA加密数据...");
    console.log("   - 原始数据长度:", data.length);

    try {
      // 1. 获取公钥
      const publicKey = await this.getPublicKey();

      // 2. 创建加密器
      const jsEncrypt = new JSEncrypt();
      jsEncrypt.setPublicKey(publicKey);

      // 3. 执行加密
      const encrypted = jsEncrypt.encrypt(data);

      if (!encrypted) {
        throw new Error("RSA加密失败,返回空值");
      }

      console.log("✅ RSA加密成功:");
      console.log("   - 加密后长度:", encrypted.length);
      console.log("   - 使用密钥ID:", this.keyId);

      return encrypted;
    } catch (error: any) {
      console.error("❌ RSA加密失败:", error);
      throw new Error(`RSA加密失败: ${error.message}`);
    }
  }

  /**
   * 批量加密多个字段
   * @param data 要加密的数据对象
   * @param fields 需要加密的字段名数组
   * @returns Promise<any> 加密后的数据对象
   */
  public async encryptFields(data: any, fields: string[]): Promise<any> {
    console.log("🔒 开始批量加密字段:", fields);

    const result = { ...data };

    for (const field of fields) {
      if (data[field]) {
        result[field] = await this.encrypt(data[field]);
        console.log(`✅ 字段 ${field} 加密完成`);
      }
    }

    return result;
  }

  /**
   * 验证公钥是否有效
   */
  public async validatePublicKey(): Promise<boolean> {
    try {
      await this.getPublicKey();
      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * 清除缓存的公钥(强制重新获取)
   */
  public clearCache(): void {
    console.log("🧹 清除RSA公钥缓存");
    this.publicKey = null;
    this.keyId = null;
    this.lastUpdateTime = 0;
  }

  /**
   * 获取当前状态信息
   */
  public getStatus() {
    return {
      hasPublicKey: !!this.publicKey,
      keyId: this.keyId,
      lastUpdateTime: this.lastUpdateTime,
      cacheValid: Date.now() - this.lastUpdateTime < this.KEY_CACHE_DURATION,
    };
  }

  // 静态方法,方便直接调用
  public static async encrypt(data: string): Promise<string> {
    return RSAUtils.getInstance().encrypt(data);
  }

  public static async encryptFields(data: any, fields: string[]): Promise<any> {
    return RSAUtils.getInstance().encryptFields(data, fields);
  }

  public static clearCache(): void {
    RSAUtils.getInstance().clearCache();
  }
}

// 导出实例,也可以直接使用
export const rsaUtils = RSAUtils.getInstance();
vue
<template>
  <div class="login_container">
    <el-row>
      <el-col :span="12" :xs="0"></el-col>
      <el-col :span="12" :xs="24">
        <el-form
          class="login_form"
          :model="loginForm"
          :rules="loginRules"
          ref="loginForms"
        >
          <h1>Hello</h1>
          <h2>欢迎来到ZZY后台管理系统</h2>

          <!-- RSA状态显示 -->
          <div class="rsa-status" :class="rsaStatus.class">
            <el-icon><Lock /></el-icon>
            {{ rsaStatus.text }}
          </div>

          <el-form-item prop="username">
            <el-input
              :prefix-icon="User"
              placeholder="用户名"
              v-model="loginForm.username"
              :disabled="isLoding"
            ></el-input>
          </el-form-item>

          <el-form-item prop="password">
            <el-input
              show-password
              :prefix-icon="Lock"
              type="password"
              placeholder="密码"
              v-model="loginForm.password"
              :disabled="isLoding"
              @keyup.enter="login"
            ></el-input>
          </el-form-item>

          <el-form-item>
            <el-button
              :loading="isLoding"
              class="login_btn"
              type="primary"
              size="default"
              @click="login"
              :disabled="!rsaReady"
            >
              {{ loginButtonText }}
            </el-button>
          </el-form-item>

          <!-- 调试信息(开发环境显示) -->
          <div v-if="isDev" class="debug-info">
            <el-collapse accordion>
              <el-collapse-item title="🔍 调试信息" name="debug">
                <div class="debug-content">
                  <p><strong>RSA状态:</strong> {{ rsaStatus.text }}</p>
                  <p>
                    <strong>密钥ID:</strong> {{ debugInfo.keyId || "未获取" }}
                  </p>
                  <p>
                    <strong>最后更新:</strong>
                    {{ debugInfo.lastUpdate || "从未更新" }}
                  </p>
                  <p>
                    <strong>原始密码:</strong>
                    {{ debugInfo.originalPassword || "未输入" }}
                  </p>
                  <p>
                    <strong>加密长度:</strong>
                    {{ debugInfo.encryptedLength || 0 }} 字符
                  </p>
                </div>
              </el-collapse-item>
            </el-collapse>
          </div>
        </el-form>
      </el-col>
    </el-row>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
import useUserStore from "@/store/modules/user";
import { User, Lock } from "@element-plus/icons-vue";
import { ElMessage, ElNotification, ElMessageBox } from "element-plus";
import { useRouter, useRoute } from "vue-router";
import { getTime } from "@/utils/times";
import { RSAUtils } from "@/utils/rsa";

defineOptions({
  name: "Login",
});

// ================== 基础变量 ==================
const userStore = useUserStore();
const $router = useRouter();
const $route = useRoute();
const isDev = import.meta.env.DEV;

// ================== 响应式数据 ==================
const isLoding = ref(false);
const loginForms = ref();
const rsaReady = ref(false);

const loginForm = reactive({
  username: "admin",
  password: "111111",
});

// 调试信息
const debugInfo = reactive({
  keyId: "",
  lastUpdate: "",
  originalPassword: "",
  encryptedLength: 0,
});

// ================== 计算属性 ==================
const rsaStatus = computed(() => {
  if (!rsaReady.value) {
    return {
      text: "🔄 正在建立安全连接...",
      class: "rsa-loading",
    };
  }
  return {
    text: "🔒 安全连接已建立",
    class: "rsa-ready",
  };
});

const loginButtonText = computed(() => {
  if (isLoding.value) return "登录中...";
  if (!rsaReady.value) return "建立安全连接中...";
  return "登录";
});

// ================== RSA初始化 ==================
/**
 * 初始化RSA加密
 */
const initRSA = async () => {
  console.log("🔐 开始初始化RSA加密...");

  try {
    // 验证RSA是否可用
    const isValid = await RSAUtils.getInstance().validatePublicKey();

    if (isValid) {
      rsaReady.value = true;

      // 更新调试信息
      if (isDev) {
        const status = RSAUtils.getInstance().getStatus();
        debugInfo.keyId = status.keyId || "";
        debugInfo.lastUpdate = status.lastUpdateTime
          ? new Date(status.lastUpdateTime).toLocaleString()
          : "";
      }

      console.log("✅ RSA加密初始化成功");
      ElMessage.success("安全连接建立成功");
    } else {
      throw new Error("RSA公钥验证失败");
    }
  } catch (error: any) {
    console.error("❌ RSA加密初始化失败:", error);
    rsaReady.value = false;

    ElMessageBox.confirm(
      "安全连接建立失败,无法保证登录安全。是否重试?",
      "安全警告",
      {
        confirmButtonText: "重试",
        cancelButtonText: "忽略",
        type: "warning",
      }
    )
      .then(() => {
        initRSA(); // 重试
      })
      .catch(() => {
        console.warn("⚠️ 用户选择忽略RSA加密");
      });
  }
};

// ================== 登录逻辑 ==================
/**
 * 登录处理
 */
const login = async () => {
  console.log("🚀 开始登录流程...");

  // 1. 表单验证
  try {
    await loginForms.value.validate();
  } catch (error) {
    console.log("📝 表单验证失败");
    return;
  }

  // 2. 检查RSA状态
  if (!rsaReady.value) {
    ElMessage.warning("安全连接未建立,请稍后再试");
    return;
  }

  isLoding.value = true;

  try {
    // 3. 加密密码
    console.log("🔒 开始加密登录密码...");
    const encryptedPassword = await RSAUtils.encrypt(loginForm.password);

    // 更新调试信息
    if (isDev) {
      debugInfo.originalPassword = loginForm.password;
      debugInfo.encryptedLength = encryptedPassword.length;
    }

    console.log("✅ 密码加密成功,长度:", encryptedPassword.length);

    // 4. 构建登录数据
    const loginData = {
      username: loginForm.username,
      password: encryptedPassword, // 加密后的密码
      timestamp: Date.now(), // 时间戳
    };

    console.log("📤 发送登录请求...");

    // 5. 调用store登录方法
    await userStore.userLogin(loginData);

    // 6. 登录成功处理
    console.log("🎉 登录成功!");

    // 跳转页面
    const redirectPath = ($route.query.redirect as string) || "/";
    await $router.push(redirectPath);

    // 显示欢迎消息
    ElNotification({
      title: `Hi ${getTime()}`,
      message: "欢迎回来!登录已加密保护",
      type: "success",
      duration: 3000,
    });
  } catch (error: any) {
    console.error("❌ 登录失败:", error);

    // 显示错误消息
    ElNotification({
      title: "登录失败",
      message: error.message || "未知错误",
      type: "error",
      duration: 5000,
    });

    // 清空密码
    loginForm.password = "";

    // 如果是RSA相关错误,重新初始化
    if (error.message?.includes("RSA") || error.message?.includes("加密")) {
      console.log("🔄 检测到RSA错误,重新初始化...");
      RSAUtils.clearCache();
      rsaReady.value = false;
      await initRSA();
    }
  } finally {
    isLoding.value = false;
  }
};

// ================== 表单验证 ==================
const validateUsername = (_rule: any, value: any, callback: any) => {
  const usernameRegex = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/;
  if (!usernameRegex.test(value)) {
    callback(new Error("用户名必须以字母开头,且长度为4-16位"));
  } else {
    callback();
  }
};

const loginRules = {
  username: [
    { required: true, validator: validateUsername, trigger: "change" },
  ],
  password: [
    { required: true, message: "密码不能为空", trigger: "change" },
    { min: 6, max: 20, message: "密码长度为6-20位", trigger: "change" },
  ],
};

// ================== 生命周期 ==================
onMounted(() => {
  console.log("📄 登录页面已挂载");
  // 初始化RSA
  initRSA();
});

onUnmounted(() => {
  console.log("📄 登录页面已卸载");
  // 清理敏感数据
  loginForm.password = "";
  debugInfo.originalPassword = "";
});
</script>

📝 总结

  • 每人一对 RSA 密钥 ← 这是端到端加密模式
  • 服务器一对 RSA 密钥 ← 这是传输加密模式

AES 加解密

什么是 AES?

AES = Advanced Encryption Standard(高级加密标准)

  • 对称加密算法:加密和解密使用同一个密钥
  • 速度很快:比 RSA 快 100-1000 倍
  • 安全性高:美国政府标准,军用级别加密
  • 支持大数据:可以加密任意大小的数据

对称加密 vs 非对称加密

RSA(非对称加密)

  • 公钥加密 → 私钥解密
  • 优点:安全性高,不需要共享密钥
  • 缺点:速度慢,只能加密小数据

AES(对称加密)

  • 密钥加密 → 同一个密钥解密
  • 优点:速度快,可以加密大数据
  • 缺点:需要安全地共享密钥

AES 工作原理(简化版)

加密过程

原始数据: "Hello World"
密钥:     "MySecretKey123456"

    【AES加密算法】

加密结果: "j8vV2K+5x9..."(乱码)

解密过程

加密数据: "j8vV2K+5x9..."
密钥:     "MySecretKey123456"(必须是同一个密钥)

    【AES解密算法】

原始数据: "Hello World"

✅ AES 优点

  • 对称加密,实现简单:加密解密用同一个密钥
  • 速度快:比 RSA 快几百倍
  • 无大小限制:可以加密任意大小的数据
  • 资源消耗低,CPU 占用很少,内存占用很少

❌ AES 缺点

1. 🔑 密钥分发困难
   - 如何安全地把密钥给对方?
   - 网络传输密钥有风险
   - 需要提前约定密钥

2. 👥 密钥管理复杂
   - 每个用户都需要不同密钥
   - 密钥泄露影响范围大
   - 密钥更新困难

3. 🚫 无法数字签名
   - 不能验证消息来源
   - 不能防止否认
   - 需要额外的认证机制

4. 🔄 密钥共享问题
   - 同一密钥加密所有数据
   - 一处泄露,全盘皆输
   - 无法区分不同用户

RSA vs AES 详细对比解析

特性RSA (非对称加密)AES (对称加密)
加密类型非对称 (公钥+私钥)对称 (同一密钥)
密钥数量2 个 (公钥+私钥)1 个 (共享密钥)
加密速度很慢很快
数据大小限制有限制 (~245 字节)无限制
密钥分发简单 (公钥可公开)复杂 (需安全传输)
主要用途密钥交换、数字签名大量数据加密
计算复杂度
密钥长度2048-4096 位128-256 位

RSA + AES 组合加密

🤔 为什么要组合使用?

ts
// ❌ 只用RSA的问题
const bigData = "一个1MB的文件内容...";
const encrypted = RSA.encrypt(bigData, publicKey);
// 💥 报错!RSA只能加密245字节

// ❌ 只用AES的问题
const data = "敏感数据";
const key = "mySecretKey123"; // 🚨 如何安全地把这个密钥给对方?
const encrypted = AES.encrypt(data, key);

组合使用的天才之处

ts
// ✅ RSA + AES 完美结合
// RSA负责:安全传输AES密钥(小数据)
// AES负责:快速加密实际数据(大数据)

前端加密传输

流程如下

阶段一:密钥准备
前端 → 请求后端RSA公钥 → 后端返回公钥 → 前端缓存公钥

阶段二:数据加密
前端生成AES密钥 → 用AES加密数据 → 用RSA加密AES密钥 → 打包发送 (用RSA公钥加密的AES密钥、加密的数据包)

阶段三:数据传输
前端发送加密包 → 网络传输 → 后端接收加密包

阶段四:数据解密
后端用RSA私钥解密AES密钥 → 用AES密钥解密数据 → 获得原始数据

阶段五:业务处理
后端处理业务逻辑 → 返回处理结果

数据包结构:

json
{
  "encryptedData": "AES加密后的数据",
  "encryptedKey": "RSA加密后的AES密钥",
  "timestamp": 1703123456789,
  "algorithm": "RSA-2048+AES-256",
  "version": "1.0"
}

密钥管理

// ✅ 正确做法
- RSA私钥只存在服务器端
- RSA公钥可以缓存,但定期更新
- AES密钥每次随机生成,用完即废

// ❌ 错误做法
- 把RSA私钥存在前端
- 重复使用同一个AES密钥
- 把密钥硬编码在代码中

后端响应加密

逻辑反转:前端生成密钥对

流程如下

加密通信:

1. 前端生成RSA密钥对(公钥 + 私钥)
2. 前端保存私钥到本地
3. 前端发送公钥到后端注册
4. 后端保存前端公钥到Redis

解密响应:

5. 前端请求敏感数据(携带keyId + requireEncrypted标识)
6. 后端检查请求标识
7. 后端生成随机AES密钥
8. 后端用AES密钥加密响应数据
9. 后端用前端公钥加密AES密钥
10. 后端返回加密包


11. 前端接收加密响应包
12. 前端用私钥解密AES密钥
13. 前端用AES密钥解密响应数据
14. 前端得到原始响应数据

防攻击措施

WARNING

  • ✅ 前端私钥永远不发送给后端
  • ✅ 前端公钥有过期时间(24 小时)
  • ✅ AES 密钥每次随机生成,用完即废
  • ✅ 使用 Redis 存储临时密钥信息

最佳实践

bash
pnpm install crypto-js jsencrypt

pnpm install @types/crypto-js --save-dev

java工具类

CryptoUtils.java
java
package com.zzy.admin.utils;

import com.alibaba.fastjson.JSON;
import com.zzy.admin.common.Result;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;

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.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * 后端AES+RSA混合加解密工具类
 * 
 * 功能特点:
 * 1. 与前端CryptoUtils完全兼容的数据格式
 * 2. 支持AES-256-CBC + RSA-2048混合加解密
 * 3. 使用统一的Result响应格式
 * 4. 完整的异常处理和日志记录
 * 5. 线程安全的工具方法
 * 6. 防重放攻击和时间戳验证
 * 
 * @author zzy
 * @version 1.0
 */
@Slf4j
@Component
public class CryptoUtils {
    
    // ==================== 算法配置常量 ====================
    private static final String RSA_ALGORITHM = "RSA";
    private static final String AES_ALGORITHM = "AES";
    private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
    private static final int RSA_KEY_SIZE = 2048;
    private static final int AES_KEY_SIZE = 256;
    private static final String ALGORITHM_IDENTIFIER = "RSA-2048+AES-256-CBC";
    private static final String VERSION = "1.0";
    private static final long TIMESTAMP_TOLERANCE = 5 * 60 * 1000L; // 5分钟时间戳容差
    
    // ==================== 数据结构定义 ====================
    
    /**
     * 统一加密数据包格式
     * 必须与前端EncryptedPackage接口完全一致
     */
    @Data
    public static class EncryptedPackage {
        private String data;        // AES加密后的数据(Base64)
        private String key;         // RSA加密后的AES密钥(Base64)
        private String iv;          // AES初始化向量(Base64)
        private Long timestamp;     // 时间戳(防重放攻击)
        private String algorithm;   // 加密算法标识
        private String version;     // 版本号
    }
    
    /**
     * 密钥和IV的组合格式
     * 用于RSA加密时将AES密钥和IV打包在一起
     */
    @Data
    private static class KeyIvCombined {
        private String key;  // AES密钥(Base64)
        private String iv;   // IV向量(Base64)
    }
    
    /**
     * RSA密钥对响应格式
     */
    @Data
    public static class RSAKeyPair {
        private String publicKey;   // RSA公钥(PEM格式)
        private String privateKey;  // RSA私钥(PEM格式)
        private String algorithm;   // 算法标识
        private Integer keySize;    // 密钥长度
        private Long generateTime;  // 生成时间
    }
    
    /**
     * 公钥信息响应格式
     */
    @Data
    public static class PublicKeyInfo {
        private String publicKey;   // RSA公钥(PEM格式)
        private String algorithm;   // 算法标识
        private Integer keySize;    // 密钥长度
        private String version;     // 版本号
        private Long timestamp;     // 时间戳
    }
    
    // ==================== 核心加解密方法 ====================
    
    /**
     * 生成RSA密钥对
     * @return Result包装的密钥对信息
     */
    public static Result<RSAKeyPair> generateRSAKeyPair() {
        try {
            log.info("🔑 开始生成RSA密钥对,密钥长度: {} 位", RSA_KEY_SIZE);
            
            // 生成密钥对
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            keyPairGenerator.initialize(RSA_KEY_SIZE);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            
            // 获取公钥和私钥
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            
            // 转换为Base64字符串
            String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
            String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());
            
            // 格式化为PEM格式
            String formattedPublicKey = formatPublicKey(publicKeyString);
            String formattedPrivateKey = formatPrivateKey(privateKeyString);
            
            // 构建响应数据
            RSAKeyPair rsaKeyPair = new RSAKeyPair();
            rsaKeyPair.setPublicKey(formattedPublicKey);
            rsaKeyPair.setPrivateKey(formattedPrivateKey);
            rsaKeyPair.setAlgorithm(ALGORITHM_IDENTIFIER);
            rsaKeyPair.setKeySize(RSA_KEY_SIZE);
            rsaKeyPair.setGenerateTime(System.currentTimeMillis());
            
            log.info("✅ RSA密钥对生成成功");
            log.info("📤 公钥长度: {} 字符", formattedPublicKey.length());
            log.info("🔐 私钥长度: {} 字符", formattedPrivateKey.length());
            
            return Result.success("RSA密钥对生成成功", rsaKeyPair);
            
        } catch (Exception e) {
            log.error("❌ 生成RSA密钥对失败: {}", e.getMessage(), e);
            return Result.fail("生成RSA密钥对失败: " + e.getMessage());
        }
    }
    
    /**
     * 创建公钥信息响应
     * @param publicKeyString RSA公钥(PEM格式)
     * @return Result包装的公钥信息
     */
    public static Result<PublicKeyInfo> createPublicKeyInfo(String publicKeyString) {
        try {
            log.info("📤 创建公钥信息响应");
            
            PublicKeyInfo publicKeyInfo = new PublicKeyInfo();
            publicKeyInfo.setPublicKey(publicKeyString);
            publicKeyInfo.setAlgorithm(ALGORITHM_IDENTIFIER);
            publicKeyInfo.setKeySize(RSA_KEY_SIZE);
            publicKeyInfo.setVersion(VERSION);
            publicKeyInfo.setTimestamp(System.currentTimeMillis());
            
            log.info("✅ 公钥信息创建成功");
            return Result.success("获取公钥成功", publicKeyInfo);
            
        } catch (Exception e) {
            log.error("❌ 创建公钥信息失败: {}", e.getMessage(), e);
            return Result.fail("获取公钥失败: " + e.getMessage());
        }
    }
    
    /**
     * 解密前端发送的加密数据包
     * @param encryptedPackage 前端发送的加密数据包
     * @param privateKeyString RSA私钥(PEM格式)
     * @param clazz 解密后数据的目标类型
     * @return Result包装的解密结果
     */
    public static <T> Result<T> decrypt(EncryptedPackage encryptedPackage, String privateKeyString, Class<T> clazz) {
        try {
            log.info("🔓 开始解密数据包...");
            log.info("📋 数据包信息: 算法={}, 版本={}, 时间戳={}", 
                encryptedPackage.getAlgorithm(), 
                encryptedPackage.getVersion(),
                encryptedPackage.getTimestamp());
            
            // 1. 验证数据包格式
            Result<Void> validateResult = validateEncryptedPackage(encryptedPackage);
            if (validateResult.isFail()) {
                return Result.fail(validateResult.getMessage());
            }
            
            // 2. 验证时间戳
            if (!isValidTimestamp(encryptedPackage.getTimestamp())) {
                log.warn("⏰ 时间戳验证失败,时间戳: {}", encryptedPackage.getTimestamp());
                return Result.fail("请求时间戳无效或已过期");
            }
            
            // 3. 解析RSA私钥
            PrivateKey privateKey = parsePrivateKey(privateKeyString);
            if (privateKey == null) {
                return Result.fail("RSA私钥解析失败");
            }
            
            // 4. 用RSA私钥解密AES密钥和IV
            KeyIvCombined keyIvCombined = decryptAESKeyWithRSA(encryptedPackage.getKey(), privateKey);
            if (keyIvCombined == null) {
                return Result.fail("AES密钥解密失败");
            }
            log.info("🔑 AES密钥解密成功");
            
            // 5. 用AES密钥解密数据
            String originalData = decryptDataWithAES(
                encryptedPackage.getData(), 
                keyIvCombined.getKey(), 
                keyIvCombined.getIv()
            );
            if (originalData == null) {
                return Result.fail("AES数据解密失败");
            }
            log.info("📦 AES数据解密成功,原始数据长度: {} 字符", originalData.length());
            
            // 6. 解析JSON数据
            T result;
            if (clazz == String.class) {
                result = clazz.cast(originalData);
            } else {
                result = JSON.parseObject(originalData, clazz);
            }
            
            log.info("✅ 数据包解密完成!");
            return Result.success("解密成功", result);
            
        } catch (Exception e) {
            log.error("❌ 解密数据包失败: {}", e.getMessage(), e);
            return Result.fail("解密失败: " + e.getMessage());
        }
    }
    
    /**
     * 加密响应数据(用于向前端返回加密响应)
     * @param data 要加密的响应数据
     * @param publicKeyString 前端的RSA公钥
     * @return Result包装的加密数据包
     */
    public static Result<EncryptedPackage> encryptResponse(Object data, String publicKeyString) {
        try {
            log.info("🔐 开始加密响应数据...");
            
            // 1. 将数据转换为JSON字符串
            String jsonString = (data instanceof String) ? (String) data : JSON.toJSONString(data);
            log.info("📝 响应数据长度: {} 字符", jsonString.length());
            
            // 2. 生成随机AES密钥和IV
            String aesKey = generateAESKey();
            String iv = generateIV();
            if (aesKey == null || iv == null) {
                return Result.fail("生成AES密钥或IV失败");
            }
            
            // 3. 用AES加密数据
            String encryptedData = encryptDataWithAES(jsonString, aesKey, iv);
            if (encryptedData == null) {
                return Result.fail("AES数据加密失败");
            }
            
            // 4. 解析前端公钥并用其加密AES密钥
            PublicKey publicKey = parsePublicKey(publicKeyString);
            if (publicKey == null) {
                return Result.fail("前端公钥解析失败");
            }
            
            KeyIvCombined keyIvCombined = new KeyIvCombined();
            keyIvCombined.setKey(aesKey);
            keyIvCombined.setIv(iv);
            
            String encryptedKey = encryptAESKeyWithRSA(JSON.toJSONString(keyIvCombined), publicKey);
            if (encryptedKey == null) {
                return Result.fail("AES密钥加密失败");
            }
            
            // 5. 构建加密数据包
            EncryptedPackage encryptedPackage = new EncryptedPackage();
            encryptedPackage.setData(encryptedData);
            encryptedPackage.setKey(encryptedKey);
            encryptedPackage.setIv(iv);
            encryptedPackage.setTimestamp(System.currentTimeMillis());
            encryptedPackage.setAlgorithm(ALGORITHM_IDENTIFIER);
            encryptedPackage.setVersion(VERSION);
            
            log.info("✅ 响应数据加密完成!");
            return Result.success("加密成功", encryptedPackage);
            
        } catch (Exception e) {
            log.error("❌ 加密响应数据失败: {}", e.getMessage(), e);
            return Result.fail("加密响应失败: " + e.getMessage());
        }
    }
    
    // ==================== 验证和工具方法 ====================
    
    /**
     * 验证加密数据包格式
     * @param pkg 加密数据包
     * @return 验证结果
     */
    private static Result<Void> validateEncryptedPackage(EncryptedPackage pkg) {
        try {
            if (pkg == null) {
                return Result.fail("加密数据包不能为空");
            }
            if (pkg.getData() == null || pkg.getData().trim().isEmpty()) {
                return Result.fail("加密数据不能为空");
            }
            if (pkg.getKey() == null || pkg.getKey().trim().isEmpty()) {
                return Result.fail("加密密钥不能为空");
            }
            if (pkg.getTimestamp() == null) {
                return Result.fail("时间戳不能为空");
            }
            if (!ALGORITHM_IDENTIFIER.equals(pkg.getAlgorithm())) {
                log.warn("⚠️ 算法标识不匹配,期望: {}, 实际: {}", ALGORITHM_IDENTIFIER, pkg.getAlgorithm());
                // 不强制要求算法匹配,只记录警告
            }
            
            return Result.success("数据包格式验证通过");
            
        } catch (Exception e) {
            log.error("❌ 验证数据包格式失败: {}", e.getMessage(), e);
            return Result.fail("数据包格式验证失败: " + e.getMessage());
        }
    }
    
    /**
     * 验证时间戳是否有效
     * @param timestamp 时间戳
     * @return 是否有效
     */
    private static boolean isValidTimestamp(Long timestamp) {
        if (timestamp == null) {
            return false;
        }
        long now = System.currentTimeMillis();
        long diff = Math.abs(now - timestamp);
        boolean valid = diff <= TIMESTAMP_TOLERANCE;
        
        if (!valid) {
            log.warn("⏰ 时间戳验证失败,当前时间: {}, 请求时间: {}, 时间差: {} 毫秒", 
                now, timestamp, diff);
        }
        
        return valid;
    }
    
    /**
     * 检查RSA密钥是否有效
     * @param publicKey RSA公钥
     * @return 是否有效
     */
    public static Result<Boolean> validateRSAKey(String publicKey) {
        try {
            if (publicKey == null || publicKey.trim().isEmpty()) {
                return Result.fail("公钥不能为空");
            }
            
            // 尝试解析公钥
            PublicKey key = parsePublicKey(publicKey);
            if (key == null) {
                return Result.fail("公钥格式无效");
            }
            
            // 测试加密功能
            Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipher.doFinal("test".getBytes(StandardCharsets.UTF_8));
            
            log.info("✅ RSA公钥验证通过");
            return Result.success("RSA密钥有效", true);
            
        } catch (Exception e) {
            log.error("❌ RSA密钥验证失败: {}", e.getMessage(), e);
            return Result.fail("RSA密钥无效: " + e.getMessage());
        }
    }
    
    // ==================== 底层加解密实现 ====================
    
    /**
     * 用RSA私钥解密AES密钥
     */
    private static KeyIvCombined decryptAESKeyWithRSA(String encryptedKey, PrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            
            byte[] encryptedBytes = Base64.decodeBase64(encryptedKey);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            
            String keyIvJson = new String(decryptedBytes, StandardCharsets.UTF_8);
            return JSON.parseObject(keyIvJson, KeyIvCombined.class);
            
        } catch (Exception e) {
            log.error("❌ RSA解密AES密钥失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 用RSA公钥加密AES密钥
     */
    private static String encryptAESKeyWithRSA(String keyIvJson, PublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            
            byte[] dataBytes = keyIvJson.getBytes(StandardCharsets.UTF_8);
            byte[] encryptedBytes = cipher.doFinal(dataBytes);
            
            return Base64.encodeBase64String(encryptedBytes);
            
        } catch (Exception e) {
            log.error("❌ RSA加密AES密钥失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 用AES解密数据
     */
    private static String decryptDataWithAES(String encryptedData, String aesKey, String iv) {
        try {
            // 解码密钥和IV
            byte[] keyBytes = Base64.decodeBase64(aesKey);
            byte[] ivBytes = Base64.decodeBase64(iv);
            
            // 创建密钥和IV规范
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, AES_ALGORITHM);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            
            // 初始化解密器
            Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
            
            // 解密数据
            byte[] encryptedBytes = Base64.decodeBase64(encryptedData);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            
            return new String(decryptedBytes, StandardCharsets.UTF_8);
            
        } catch (Exception e) {
            log.error("❌ AES解密数据失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 用AES加密数据
     */
    private static String encryptDataWithAES(String data, String aesKey, String iv) {
        try {
            // 解码密钥和IV
            byte[] keyBytes = Base64.decodeBase64(aesKey);
            byte[] ivBytes = Base64.decodeBase64(iv);
            
            // 创建密钥和IV规范
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, AES_ALGORITHM);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            
            // 初始化加密器
            Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
            
            // 加密数据
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            byte[] encryptedBytes = cipher.doFinal(dataBytes);
            
            return Base64.encodeBase64String(encryptedBytes);
            
        } catch (Exception e) {
            log.error("❌ AES加密数据失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 生成随机AES密钥
     */
    private static String generateAESKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
            keyGenerator.init(AES_KEY_SIZE);
            SecretKey secretKey = keyGenerator.generateKey();
            return Base64.encodeBase64String(secretKey.getEncoded());
        } catch (Exception e) {
            log.error("❌ 生成AES密钥失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 生成随机IV
     */
    private static String generateIV() {
        try {
            byte[] iv = new byte[16]; // AES块大小为16字节
            new SecureRandom().nextBytes(iv);
            return Base64.encodeBase64String(iv);
        } catch (Exception e) {
            log.error("❌ 生成IV失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 解析PEM格式的RSA私钥
     */
    private static PrivateKey parsePrivateKey(String privateKeyString) {
        try {
            if (privateKeyString == null || privateKeyString.trim().isEmpty()) {
                return null;
            }
            
            // 清理PEM格式
            String cleanKey = privateKeyString
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
            
            byte[] keyBytes = Base64.decodeBase64(cleanKey);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            
            return keyFactory.generatePrivate(keySpec);
            
        } catch (Exception e) {
            log.error("❌ 解析RSA私钥失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 解析PEM格式的RSA公钥
     */
    private static PublicKey parsePublicKey(String publicKeyString) {
        try {
            if (publicKeyString == null || publicKeyString.trim().isEmpty()) {
                return null;
            }
            
            // 清理PEM格式
            String cleanKey = publicKeyString
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s", "");
            
            byte[] keyBytes = Base64.decodeBase64(cleanKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            
            return keyFactory.generatePublic(keySpec);
            
        } catch (Exception e) {
            log.error("❌ 解析RSA公钥失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 格式化公钥为PEM格式
     */
    private static String formatPublicKey(String publicKey) {
        StringBuilder sb = new StringBuilder();
        sb.append("-----BEGIN PUBLIC KEY-----\n");
        
        // 每64个字符一行
        for (int i = 0; i < publicKey.length(); i += 64) {
            int end = Math.min(i + 64, publicKey.length());
            sb.append(publicKey, i, end).append("\n");
        }
        
        sb.append("-----END PUBLIC KEY-----");
        return sb.toString();
    }
    
    /**
     * 格式化私钥为PEM格式
     */
    private static String formatPrivateKey(String privateKey) {
        StringBuilder sb = new StringBuilder();
        sb.append("-----BEGIN PRIVATE KEY-----\n");
        
        // 每64个字符一行
        for (int i = 0; i < privateKey.length(); i += 64) {
            int end = Math.min(i + 64, privateKey.length());
            sb.append(privateKey, i, end).append("\n");
        }
        
        sb.append("-----END PRIVATE KEY-----");
        return sb.toString();
    }
}

ts工具类

ts
// utils/cryptoUtils.ts
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'
import request from '@/utils/request' // 使用你现有的axios

// ==================== 数据格式定义 ====================

/**
 * 加密数据包格式(和后端保持一致)
 */
export interface EncryptedPackage {
  data: string           // AES加密后的数据
  key: string            // RSA加密后的AES密钥
  iv: string             // AES的IV
  timestamp: number      // 时间戳
  algorithm: string      // 算法标识
  version: string        // 版本号
}

/**
 * 🔐 简化版加密工具类
 * 
 * 只有3个主要方法:
 * 1. getPublicKey() - 获取服务器公钥
 * 2. encrypt() - 加密数据
 * 3. sendRequest() - 发送加密请求
 */
export class CryptoUtils {
  
  // 缓存服务器公钥
  private static serverPublicKey: string = ''
  
  /**
   * 📥 第一步:获取服务器公钥
   * 
   * 什么时候用:应用启动时调用一次就行
   * 
   * @example
   * ```typescript
   * // 在App.vue或main.ts中调用
   * await CryptoUtils.getPublicKey()
   * ```
   */
  static async getPublicKey(): Promise<boolean> {
    try {
      console.log('📥 获取服务器公钥...')
      
      // 调用后端接口获取公钥
      const result = await request({
        url: '/crypto/public-key',
        method: 'GET'
      })
      
      // 保存公钥
      this.serverPublicKey = result.publicKey
      
      console.log('✅ 公钥获取成功')
      return true
      
    } catch (error) {
      console.error('❌ 获取公钥失败:', error)
      return false
    }
  }
  
  /**
   * 🔐 第二步:加密数据
   * 
   * 什么时候用:要发送敏感数据时
   * 
   * @param data 要加密的数据(对象、字符串都行)
   * @returns 加密后的数据包
   * 
   * @example
   * ```typescript
   * // 加密登录数据
   * const loginData = { username: 'admin', password: '123456' }
   * const encrypted = await CryptoUtils.encrypt(loginData)
   * ```
   */
  static async encrypt(data: any): Promise<EncryptedPackage> {
    try {
      console.log('🔐 开始加密数据...')
      
      // 1. 检查是否有公钥
      if (!this.serverPublicKey) {
        console.log('🔑 没有公钥,先获取公钥...')
        const success = await this.getPublicKey()
        if (!success) {
          throw new Error('获取公钥失败')
        }
      }
      
      // 2. 把数据转成JSON字符串
      const jsonString = typeof data === 'string' ? data : JSON.stringify(data)
      console.log('📝 数据长度:', jsonString.length, '字符')
      
      // 3. 生成随机的AES密钥和IV
      const aesKey = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Base64)  // 32字节 = 256位
      const iv = CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Base64)      // 16字节 = 128位
      console.log('🔑 AES密钥和IV生成完成')
      
      // 4. 用AES加密数据(这个快,适合大数据)
      const aesKeyWords = CryptoJS.enc.Base64.parse(aesKey)
      const ivWords = CryptoJS.enc.Base64.parse(iv)
      
      const encryptedData = CryptoJS.AES.encrypt(jsonString, aesKeyWords, {
        iv: ivWords,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
      }).toString()
      console.log('📦 AES加密完成')
      
      // 5. 用RSA加密AES密钥(这个安全,但只能加密小数据)
      const jsEncrypt = new JSEncrypt()
      jsEncrypt.setPublicKey(this.serverPublicKey)
      
      // 把AES密钥和IV打包成JSON,然后用RSA加密
      const keyInfo = JSON.stringify({ key: aesKey, iv: iv })
      const encryptedKey = jsEncrypt.encrypt(keyInfo)
      
      if (!encryptedKey) {
        throw new Error('RSA加密失败')
      }
      console.log('🔐 RSA加密完成')
      
      // 6. 打包返回
      const result: EncryptedPackage = {
        data: encryptedData,
        key: encryptedKey,
        iv: iv,
        timestamp: Date.now(),
        algorithm: 'RSA-2048+AES-256-CBC',
        version: '1.0'
      }
      
      console.log('✅ 加密完成!')
      return result
      
    } catch (error) {
      console.error('❌ 加密失败:', error)
      throw new Error(`加密失败: ${error.message}`)
    }
  }
  
  /**
   * 📤 第三步:发送加密请求
   * 
   * 什么时候用:要发送敏感数据到后端时
   * 
   * @param url 接口地址
   * @param data 要发送的数据
   * @returns 后端响应的数据
   * 
   * @example
   * ```typescript
   * // 发送加密登录请求
   * const loginData = { username: 'admin', password: '123456' }
   * const result = await CryptoUtils.sendRequest('/auth/encrypted-login', loginData)
   * 
   * // 发送加密的用户信息
   * const userInfo = { phone: '13800138000', idCard: '123456789' }
   * await CryptoUtils.sendRequest('/user/update', userInfo)
   * ```
   */
  static async sendRequest<T = any>(url: string, data: any): Promise<T> {
    try {
      console.log('📤 发送加密请求:', url)
      
      // 1. 先加密数据
      const encryptedPackage = await this.encrypt(data)
      
      // 2. 发送请求(用你现有的request实例)
      const response: T = await request({
        url: url,
        method: 'POST',
        data: encryptedPackage
      })
      
      console.log('✅ 请求发送成功')
      return response
      
    } catch (error) {
      console.error('❌ 发送请求失败:', error)
      throw error
    }
  }
  
  /**
   * 📊 查看当前状态
   * 
   * 什么时候用:调试时看看工具是否正常
   */
  static getStatus() {
    return {
      hasPublicKey: !!this.serverPublicKey,
      publicKeyLength: this.serverPublicKey.length
    }
  }
  
  /**
   * 🗑️ 清除公钥
   * 
   * 什么时候用:切换环境或重新初始化时
   */
  static clearPublicKey() {
    this.serverPublicKey = ''
    console.log('🗑️ 公钥已清除')
  }
}

// 导出简化的使用方法
export const encrypt = CryptoUtils.encrypt.bind(CryptoUtils)
export const sendEncryptedRequest = CryptoUtils.sendRequest.bind(CryptoUtils)

🔤 Base64编码在加密中的作用

🤔 为什么要转为Base64?

核心原因:二进制数据传输问题

java
// 原始密钥是什么样的?
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();

// 获取原始字节数据
byte[] publicKeyBytes = publicKey.getEncoded();
System.out.println("原始字节数据: " + Arrays.toString(publicKeyBytes));
// 输出:[48, -126, 1, 34, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1...]
// 这是二进制数据,包含不可打印字符!

// 如果直接转字符串会怎样?
String badString = new String(publicKeyBytes);
System.out.println("直接转字符串: " + badString);
// 输出:0☺"0 ♪*☻H☻ù ♪♪☺♪☺☺♪♪○...乱码!❌

数据传输的挑战

ts
// 网络传输场景
const request = {
    publicKey: "0☺\"0\r\u0006\t*☻H☻ù\r\u0001\u0001..."  // ❌ 包含控制字符
}

// JSON序列化会出错
JSON.stringify(request)  // ❌ 可能报错或丢失数据

// HTTP传输也会有问题
fetch('/api/keys', {
    body: JSON.stringify(request)  // ❌ 特殊字符可能被破坏
})

🎯 Base64解决了什么问题?

Base64的特点

txt
Base64字符集:A-Z, a-z, 0-9, +, /(64个字符)
- 所有字符都是可打印的
- 不包含控制字符
- 安全传输通过HTTP、JSON、XML等文本协议
- 不会被各种系统误解或破坏

编码前后对比

java
// 编码前:二进制字节
byte[] keyBytes = {48, -126, 1, 34, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13};
System.out.println("二进制: " + Arrays.toString(keyBytes));
// 输出:[48, -126, 1, 34, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13]

// 编码后:Base64字符串
String base64String = Base64.encodeBase64String(keyBytes);
System.out.println("Base64: " + base64String);
// 输出:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
// 全部是可打印字符!✅