当前位置: 首页 > article >正文

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

目录

一、后端实现

 二、前端实现(Vue2)

三、补充

 1.增强安全措施

 四、最后说明


步骤大致如下:

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

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

一、后端实现

  • 新增AES工具类:
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));
	}
}
  •  修改请求处理切面:
/**
 * 解密切面
 */
@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);
		}
	}
}
  • 新增注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedDecrypt {
}
  • 新增公私钥管理类
/**
 * 生成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
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;
	}
}
  • 新增获取公钥接口 
@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)

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

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

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')
}
  • 修改请求拦截器: 
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
})

三、补充

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

 1.增强安全措施

  • 密钥时效性

// 前端每次请求生成新密钥
const aes = generateAesKey()
  • 完整性校验

// 后端解密后可添加HMAC校验
String hmac = json.getString("hmac");
if(!verifyHMAC(decryptedData, hmac, aesKey)) {
    throw new SecurityException("Data tampered");
}
  • 防御重放攻击

// 前端添加时间戳和随机数
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. 在网关层实现解密拦截器(而非应用层)

 

 

 

 

 

 

 

     

     

     

     

     

     


      http://www.kler.cn/a/584950.html

      相关文章:

    1. 训练数据重复采样,让正负样本比例1:1
    2. 【开源项目-爬虫】Firecrawl
    3. MySQL行列转化
    4. 开VR大空间体验馆,如何最低成本获取最大收入?
    5. 深度学习环境配置指令大全
    6. go-文件缓存与锁
    7. C#中除了Dictionary,List,HashSet,HashTable 还有哪些可以保存列表的数据类型?
    8. 批量将 Excel 文档中的图片提取到文件夹
    9. 如何学习VBA_3.2.20:DTP与Datepicker实现日期的输入
    10. 罗德与施瓦茨RTO1044,数字示波器
    11. 大数据面试之路 (一) 数据倾斜
    12. C++程序设计语言笔记——基本功能:异常处理
    13. 如何接入DeepSeek布局企业AI系统开发技术
    14. JVM内存结构笔记01-运行时数据区域
    15. 记录致远OA服务器硬盘升级过程
    16. Qt常用控件之水平布局QHBoxLayout
    17. node基础
    18. 【YOLOv8】YOLOv8改进系列(6)----替换主干网络之VanillaNet
    19. Python 机器学习小项目:手写数字识别(MNIST 数据集)
    20. 蓝桥杯备赛-基础练习 day1