Spring Boot + Vue 基于RSA+AES的混合加密

目录

一、后端实现

二、前端实现(Vue2)

三、补充

1.增强安全措施

四、最后说明


步骤大致如下:

  1. 后端生成RSA密钥对,提供公钥接口。
  2. 前端请求公钥,生成随机AES密钥和IV。
  3. 用RSA公钥加密AES密钥,用AES密钥加密数据。
  4. 发送包含加密后的AES密钥和数据的请求体。
  5. 后端用RSA私钥解密AES密钥,再用AES密钥解密数据。
  6. 使用注解和拦截器自动处理解密过程。

需要确保每个步骤都正确实现,特别是加密模式、填充方式以及编码解码的一致性,避免因配置不同导致解密失败。有什么没加入的在评论区艾特我,我进行补充

一、后端实现

  • 新增AES工具类:
java 复制代码
public class AesUtils {
	public static String encrypt(String data, String key, String iv) throws Exception {
		SecretKeySpec keySpec = new SecretKeySpec(
				Base64.getDecoder().decode(key), "AES"
		);
		IvParameterSpec ivSpec = new IvParameterSpec(
				Base64.getDecoder().decode(iv)
		);

		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
		byte[] encrypted = cipher.doFinal(data.getBytes());
		return Base64.getEncoder().encodeToString(encrypted);
	}

	public static String decrypt(String data, String key, String iv) throws Exception {
		byte[] keyBytes = Base64.getDecoder().decode(key);
		byte[] ivBytes = Base64.getDecoder().decode(iv);
		byte[] encryptedBytes = Base64.getDecoder().decode(data);

		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
		IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

		cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
		return new String(cipher.doFinal(encryptedBytes));
	}
}
  • 修改请求处理切面:
java 复制代码
/**
 * 解密切面
 */
@ControllerAdvice
public class DecryptAdvice extends RequestBodyAdviceAdapter {
	private final RsaKeyManager keyManager;

	public DecryptAdvice(RsaKeyManager keyManager) {
		this.keyManager = keyManager;
	}

	@Override
	public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		return methodParameter.hasMethodAnnotation(NeedDecrypt.class);
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
	                                       MethodParameter parameter,
	                                       Type targetType,
	                                       Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		try {
			String encryptedBody = new String(inputMessage.getBody().readAllBytes());
			JSONObject json = JSONObject.parseObject(encryptedBody);

			// 解密 AES 密钥
			String encryptedAesKey = json.getString("encryptedKey");
			String aesKey = RsaUtils.decryptByPrivateKey(encryptedAesKey, keyManager.getPrivateKey());

			// 解密数据
			String decryptedData = AesUtils.decrypt(
					json.getString("encryptedData"),
					aesKey,
					json.getString("iv")
			);
			return new DecryptedHttpInputMessage(
					new ByteArrayInputStream(decryptedData.getBytes()),
					inputMessage.getHeaders()
			);
		} catch (Exception e) {
			throw new RuntimeException("解密失败", e);
		}
	}
}
  • 新增注解
java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedDecrypt {
}
  • 新增公私钥管理类
java 复制代码
/**
 * 生成RSA
 */
public class RsaKeyManager {

	Logger log = LoggerFactory.getLogger(RsaKeyManager.class);

	private final RedisService redisService;

	// Redis 键名
	private static final String PUBLIC_KEY = "rsa:public";
	private static final String PRIVATE_KEY = "rsa:private";

	public RsaKeyManager(RedisService redisService) {
		this.redisService = redisService;
	}
	/**
	 * 初始化密钥(全局唯一)
	 */
	@PostConstruct
	public void initKeyPair() throws Exception {
		// 使用 SETNX 原子操作确保只有一个服务生成密钥
		Boolean isAbsent = redisService.setIfAbsent(PUBLIC_KEY, "");
		if (Boolean.TRUE.equals(isAbsent)) {
			KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
			generator.initialize(2048);
			KeyPair keyPair = generator.generateKeyPair();
			// 存储密钥
			redisService.set(PUBLIC_KEY,
					Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())
			);
			redisService.set(PRIVATE_KEY,
					Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())
			);
			log.info("---------------------------初始化RSA秘钥---------------------------");
		}
	}
	/**
	 * 获取公钥
	 */
	public String getPublicKey() {
		Object publicKey = redisService.get(PUBLIC_KEY);
		return Objects.isNull(publicKey)?null:publicKey.toString();
	}

	/**
	 * 获取私钥
	 */
	public String getPrivateKey() {
		Object privateKey = redisService.get(PRIVATE_KEY);
		return Objects.isNull(privateKey)?null:privateKey.toString();
	}
}
  • 新增DecryptedHttpInputMessage
java 复制代码
public class DecryptedHttpInputMessage implements HttpInputMessage {
	private final InputStream body;
	private final HttpHeaders headers;

	public DecryptedHttpInputMessage(InputStream body, HttpHeaders headers) {
		this.body = body;
		this.headers = headers;
	}

	@Override
	public InputStream getBody() throws IOException {
		return this.body;
	}

	@Override
	public HttpHeaders getHeaders() {
		return this.headers;
	}
}
  • 新增获取公钥接口
java 复制代码
@RestController
@RequestMapping("/rsa")
public class RSAController {

	@Autowired
	private RsaKeyManager rsaKeyManager;

	/**
	 * 获取公钥
	 * @return 结果
	 */
	@GetMapping("/publicKey")
	public R<String> getPublicKey() {
		String publicKey = rsaKeyManager.getPublicKey();
		return R.ok(publicKey);
	}

}

二、前端实现(Vue2)

  • 安装新依赖:
java 复制代码
npm install crypto-js
  • 加密工具(src/utils/crypto.js):

getPublicKey 为请求公钥的接口,需要按照自己请求方式去获取

java 复制代码
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
import { getPublicKey } from '../request/api/auth'

// 初始化公钥
export async function initPublicKey() { 
    try {
        const res = await getPublicKey()
        return formatPublicKey(res.data)
    } catch (error) {
        console.error('公钥获取失败:', error)
        throw new Error('安全模块初始化失败')
    }
}

// 生成AES密钥
export function generateAesKey() {
    const key = CryptoJS.lib.WordArray.random(32)
    const iv = CryptoJS.lib.WordArray.random(16)
    return {
        key: CryptoJS.enc.Base64.stringify(key),
        iv: CryptoJS.enc.Base64.stringify(iv)
    }
}

// AES加密
export function aesEncrypt(data, key, iv) {
    const encrypted = CryptoJS.AES.encrypt(
        JSON.stringify(data),
        CryptoJS.enc.Base64.parse(key), 
        { 
            iv: CryptoJS.enc.Base64.parse(iv),
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }
    )
    return encrypted.toString()
}

// 格式化公钥
function formatPublicKey(rawKey) {
    return `-----BEGIN PUBLIC KEY-----\n${wrapKey(rawKey)}\n-----END PUBLIC KEY-----`
}

// 每64字符换行
function wrapKey(key) {
    return key.match(/.{1,64}/g).join('\n')
}
  • 修改请求拦截器:
javascript 复制代码
service.interceptors.request.use(async config => {
    if (config.needEncrypt) {
        await initPublicKey(service)
        
        // 生成AES密钥
        const aes = generateAesKey()
        
        // 加密数据
        const encryptedData = aesEncrypt(config.data, aes.key, aes.iv)
        
        // 加密AES密钥
        const encryptor = new JSEncrypt()
        encryptor.setPublicKey(publicKey)
        const encryptedKey = encryptor.encrypt(aes.key)
        
        // 构造请求体
        config.data = {
            encryptedKey: encryptedKey,
            encryptedData: encryptedData,
            iv: aes.iv
        }
    }
    return config
})

三、补充

  • 后端需要加密的接口示例
java 复制代码
@PostMapping("/secure-data")
@NeedDecrypt
public String handleSecureData(@RequestBody Map<String, Object> decryptedData) {
    return "Decrypted data: " + decryptedData.toString();
}
  • 请求结构体
java 复制代码
{
    "encryptedKey": "RSA加密后的AES密钥",
    "encryptedData": "AES加密后的数据",
    "iv": "Base64编码的IV"
}

1.增强安全措施

  • 密钥时效性
java 复制代码
// 前端每次请求生成新密钥
const aes = generateAesKey()
  • 完整性校验
java 复制代码
// 后端解密后可添加HMAC校验
String hmac = json.getString("hmac");
if(!verifyHMAC(decryptedData, hmac, aesKey)) {
    throw new SecurityException("Data tampered");
}
  • 防御重放攻击
java 复制代码
// 前端添加时间戳和随机数
config.data.timestamp = Date.now()
config.data.nonce = Math.random().toString(36).substr(2)

四、最后说明

该方案相比纯RSA加密有以下优势:

  1. 性能提升:AES加密大数据效率比RSA高1000倍以上

  2. 前向安全性:每次请求使用不同AES密钥

  3. 安全性增强:CBC模式+随机IV避免模式分析攻击

实际部署时需注意:

  1. 使用HTTPS传输加密后的数据

  2. 定期轮换RSA密钥对

  3. 对敏感接口添加频率限制

  4. 在网关层实现解密拦截器(而非应用层)

相关推荐
奶糖 肥晨16 分钟前
基于 Vue 和 Element Plus 的时间范围控制与数据展示
前端·vue.js·elementui
papapa键盘侠22 分钟前
完善机器人:让 DeepSeek 使用Vue Element UI快速搭建 AI 交互页面
vue.js·ui·机器人
FE_C_P小麦1 小时前
vue react前端项目打包部署后访问完整路由404【解决方案】
vue.js·nginx·react.js
竣峰1 小时前
简单商品管理页开发-基于yzpass-admin-template 后台管理系统模版
前端·后端
Victor3561 小时前
Zookeeper(107)Zookeeper的观察者(Watcher)机制是如何实现的?
后端
福大大架构师每日一题1 小时前
2025-03-15:判断矩形的两个角落是否可达。用go语言,给定两个正整数 xCorner 和 yCorner,以及一个二维整数数组 circles,表示若干
后端
小麦碳酸果汁1 小时前
Vue响应式原理解析
vue.js
吱吱喔喔1 小时前
NET Core中负责依赖注入和控制反转的核心组件有两个:IServiceCollection和IServiceProvider
经验分享·后端·中间件·架构·c#·依赖倒置原则
编程火箭车1 小时前
Java对象的"自我介绍术":彻底搞懂toString()魔法🔮
java·后端·源码
DBLens数据库管理和开发工具1 小时前
基于k3s部署Nginx、MySQL、SpringBoot和Redis的详细教程
后端