Spring Boot + Vue 基于RSA+AES的混合加密
目录
一、后端实现
二、前端实现(Vue2)
三、补充
1.增强安全措施
四、最后说明
步骤大致如下:
- 后端生成RSA密钥对,提供公钥接口。
- 前端请求公钥,生成随机AES密钥和IV。
- 用RSA公钥加密AES密钥,用AES密钥加密数据。
- 发送包含加密后的AES密钥和数据的请求体。
- 后端用RSA私钥解密AES密钥,再用AES密钥解密数据。
- 使用注解和拦截器自动处理解密过程。
需要确保每个步骤都正确实现,特别是加密模式、填充方式以及编码解码的一致性,避免因配置不同导致解密失败。有什么没加入的在评论区艾特我,我进行补充
一、后端实现
- 新增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加密有以下优势:
-
性能提升:AES加密大数据效率比RSA高1000倍以上
-
前向安全性:每次请求使用不同AES密钥
-
安全性增强:CBC模式+随机IV避免模式分析攻击
实际部署时需注意:
-
使用HTTPS传输加密后的数据
-
定期轮换RSA密钥对
-
对敏感接口添加频率限制
-
在网关层实现解密拦截器(而非应用层)