SpringBoot實(shí)現(xiàn)RSA+AES自動(dòng)接口解密
引言
在現(xiàn)代應(yīng)用開(kāi)發(fā)中,接口安全性變得越來(lái)越重要。當(dāng)敏感數(shù)據(jù)通過(guò)網(wǎng)絡(luò)傳輸時(shí),如何確保數(shù)據(jù)不被竊取或篡改?本文將詳細(xì)介紹如何在 SpringBoot 應(yīng)用中實(shí)現(xiàn) RSA+AES 混合加密方案,為接口通信提供強(qiáng)大的安全保障。
為什么需要接口加密?
在沒(méi)有加密的情況下,通過(guò)網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)可以被抓包工具輕松獲取。特別是當(dāng)傳輸包含用戶隱私、支付信息等敏感數(shù)據(jù)時(shí),這種風(fēng)險(xiǎn)更加不可接受。接口加密能夠確保即使數(shù)據(jù)被截獲,攻擊者也無(wú)法理解其中的內(nèi)容。
RSA+AES 混合加密方案的優(yōu)勢(shì)
我們選擇 RSA+AES 混合加密方案是因?yàn)樗Y(jié)合了兩種算法的優(yōu)點(diǎn):
- RSA: 非對(duì)稱加密,安全性高,但加密速度較慢,適合加密少量數(shù)據(jù)
- AES: 對(duì)稱加密,加密速度快,適合大量數(shù)據(jù)加密,但密鑰分發(fā)是個(gè)難題
通過(guò)混合使用這兩種算法,我們用 RSA 來(lái)加密 AES 的密鑰,然后用 AES 來(lái)加密實(shí)際傳輸?shù)臄?shù)據(jù),既保證了安全性,又兼顧了性能。
實(shí)現(xiàn)原理
- 客戶端與服務(wù)端預(yù)先約定 RSA 公鑰和私鑰
- 客戶端隨機(jī)生成 AES 密鑰,使用 RSA 公鑰加密這個(gè) AES 密鑰
- 使用 AES 密鑰加密實(shí)際請(qǐng)求數(shù)據(jù)
- 將加密后的 AES 密鑰和加密后的數(shù)據(jù)一起發(fā)送給服務(wù)端
- 服務(wù)端先用 RSA 私鑰解密得到 AES 密鑰,再用 AES 密鑰解密數(shù)據(jù)
下面我們將通過(guò)實(shí)例代碼演示如何在 SpringBoot 中實(shí)現(xiàn)這一方案。
項(xiàng)目依賴
首先在 pom.xml 中添加必要的依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
</dependencies>
加密工具類
下面是我們需要實(shí)現(xiàn)的加密工具類:
package com.example.secureapi.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
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.Base64;
public class EncryptionUtils {
// 添加BouncyCastle作為安全提供者,提供更強(qiáng)大的加密算法支持
static {
Security.addProvider(new BouncyCastleProvider());
}
// AES加密配置
private static final String AES_ALGORITHM = "AES/CBC/PKCS7Padding"; // 使用CBC模式和PKCS7填充
private static final int AES_KEY_SIZE = 256; // 使用256位密鑰提供更強(qiáng)的安全性
// RSA加密配置
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 使用PKCS1填充
private static final int RSA_KEY_SIZE = 2048; // 使用2048位密鑰長(zhǎng)度,提供足夠的安全強(qiáng)度
/**
* 生成RSA密鑰對(duì)
* @return 包含公鑰和私鑰的密鑰對(duì)
* @throws Exception 生成過(guò)程中可能出現(xiàn)的異常
*/
public static KeyPair generateRSAKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(RSA_KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
/**
* 將密鑰轉(zhuǎn)換為Base64編碼字符串,便于存儲(chǔ)和傳輸
* @param key 密鑰
* @return Base64編碼的密鑰字符串
*/
public static String keyToString(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 從Base64編碼的字符串恢復(fù)RSA公鑰
* @param keyStr Base64編碼的公鑰字符串
* @return RSA公鑰對(duì)象
* @throws Exception 轉(zhuǎn)換過(guò)程中可能出現(xiàn)的異常
*/
public static PublicKey stringToRSAPublicKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
/**
* 從Base64編碼的字符串恢復(fù)RSA私鑰
* @param keyStr Base64編碼的私鑰字符串
* @return RSA私鑰對(duì)象
* @throws Exception 轉(zhuǎn)換過(guò)程中可能出現(xiàn)的異常
*/
public static PrivateKey stringToRSAPrivateKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
/**
* 生成隨機(jī)AES密鑰
* @return AES密鑰
* @throws Exception 生成過(guò)程中可能出現(xiàn)的異常
*/
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(AES_KEY_SIZE);
return keyGen.generateKey();
}
/**
* 從Base64編碼的字符串恢復(fù)AES密鑰
* @param keyStr Base64編碼的AES密鑰字符串
* @return AES密鑰對(duì)象
*/
public static SecretKey stringToAESKey(String keyStr) {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
return new SecretKeySpec(keyBytes, "AES");
}
/**
* 使用RSA公鑰加密數(shù)據(jù)
* @param data 待加密數(shù)據(jù)
* @param publicKey RSA公鑰
* @return Base64編碼的加密數(shù)據(jù)
* @throws Exception 加密過(guò)程中可能出現(xiàn)的異常
*/
public static String encryptWithRSA(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
/**
* 使用RSA私鑰解密數(shù)據(jù)
* @param encryptedData Base64編碼的加密數(shù)據(jù)
* @param privateKey RSA私鑰
* @return 解密后的原始數(shù)據(jù)
* @throws Exception 解密過(guò)程中可能出現(xiàn)的異常
*/
public static String decryptWithRSA(String encryptedData, PrivateKey privateKey) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
/**
* 使用AES密鑰加密數(shù)據(jù)
* @param data 待加密數(shù)據(jù)
* @param secretKey AES密鑰
* @param iv 初始化向量
* @return Base64編碼的加密數(shù)據(jù)
* @throws Exception 加密過(guò)程中可能出現(xiàn)的異常
*/
public static String encryptWithAES(String data, SecretKey secretKey, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
/**
* 使用AES密鑰解密數(shù)據(jù)
* @param encryptedData Base64編碼的加密數(shù)據(jù)
* @param secretKey AES密鑰
* @param iv 初始化向量
* @return 解密后的原始數(shù)據(jù)
* @throws Exception 解密過(guò)程中可能出現(xiàn)的異常
*/
public static String decryptWithAES(String encryptedData, SecretKey secretKey, byte[] iv) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
/**
* 生成隨機(jī)初始化向量
* @return 16字節(jié)的隨機(jī)初始化向量
*/
public static byte[] generateIV() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16]; // AES使用16字節(jié)的初始化向量
random.nextBytes(iv);
return iv;
}
}
自定義請(qǐng)求包裝類
為了實(shí)現(xiàn)加密請(qǐng)求的自動(dòng)解密,我們需要定義一個(gè)請(qǐng)求包裝類:
package com.example.secureapi.model;
import lombok.Data;
@Data
public class EncryptedRequest {
// 使用RSA加密后的AES密鑰
private String encryptedKey;
// 使用Base64編碼的AES初始化向量
private String iv;
// 使用AES加密后的業(yè)務(wù)數(shù)據(jù)
private String encryptedData;
// 可選:時(shí)間戳,用于防重放攻擊
private Long timestamp;
// 可選:簽名,用于驗(yàn)證請(qǐng)求完整性
private String signature;
}
Spring Boot 解密攔截器
我們接下來(lái)實(shí)現(xiàn)一個(gè)請(qǐng)求解密攔截器,它會(huì)在控制器處理請(qǐng)求之前自動(dòng)解密數(shù)據(jù):
package com.example.secureapi.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.secureapi.annotation.Decrypt;
import com.example.secureapi.model.EncryptedRequest;
import com.example.secureapi.utils.EncryptionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.stream.Collectors;
@Slf4j
@Component
public class DecryptInterceptor implements HandlerInterceptor {
// 從配置文件注入RSA私鑰,用于解密AES密鑰
@Value("${security.rsa.private-key}")
private String rsaPrivateKeyStr;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只處理帶有@Decrypt注解的控制器方法
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 檢查方法或類是否有@Decrypt注解
Decrypt decryptAnnotation = handlerMethod.getMethodAnnotation(Decrypt.class);
if (decryptAnnotation == null) {
decryptAnnotation = handlerMethod.getBeanType().getAnnotation(Decrypt.class);
}
// 如果有@Decrypt注解,進(jìn)行解密處理
if (decryptAnnotation != null) {
// 讀取請(qǐng)求體內(nèi)容
String requestBody = request.getReader().lines().collect(Collectors.joining());
// 解析加密的請(qǐng)求對(duì)象
EncryptedRequest encryptedRequest = JSON.parseObject(requestBody, EncryptedRequest.class);
// 獲取RSA私鑰
PrivateKey rsaPrivateKey = EncryptionUtils.stringToRSAPrivateKey(rsaPrivateKeyStr);
// 解密AES密鑰
String aesKeyStr = EncryptionUtils.decryptWithRSA(encryptedRequest.getEncryptedKey(), rsaPrivateKey);
SecretKey aesKey = EncryptionUtils.stringToAESKey(aesKeyStr);
// 獲取初始化向量
byte[] iv = Base64.getDecoder().decode(encryptedRequest.getIv());
// 使用AES密鑰解密實(shí)際數(shù)據(jù)
String decryptedData = EncryptionUtils.decryptWithAES(encryptedRequest.getEncryptedData(), aesKey, iv);
// 防重放攻擊檢查(可選)
if (encryptedRequest.getTimestamp() != null) {
long currentTime = System.currentTimeMillis();
// 如果請(qǐng)求時(shí)間戳與當(dāng)前時(shí)間相差超過(guò)5分鐘,則拒絕請(qǐng)求
if (Math.abs(currentTime - encryptedRequest.getTimestamp()) > 300000) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{"code":403,"message":"請(qǐng)求已過(guò)期"}");
return false;
}
}
log.debug("解密后的數(shù)據(jù): {}", decryptedData);
// 創(chuàng)建一個(gè)包含解密數(shù)據(jù)的新BufferedReader,替換原有的請(qǐng)求Reader
request.setAttribute("DECRYPTED_DATA", decryptedData);
// 包裝請(qǐng)求,使控制器能夠讀取解密后的數(shù)據(jù)
return wrapRequest(request, decryptedData);
}
}
return true;
}
/**
* 包裝HttpServletRequest,替換請(qǐng)求體內(nèi)容為解密后的數(shù)據(jù)
* @param request 原始請(qǐng)求
* @param decryptedData 解密后的數(shù)據(jù)
* @return 是否成功包裝請(qǐng)求
*/
private boolean wrapRequest(HttpServletRequest request, String decryptedData) {
try {
// 創(chuàng)建包裝后的請(qǐng)求對(duì)象
DecryptedRequestWrapper wrapper = new DecryptedRequestWrapper(request, decryptedData);
// 替換當(dāng)前請(qǐng)求
request.setAttribute("org.springframework.web.util.WebUtils.ERROR_EXCEPTION_ATTRIBUTE", wrapper);
return true;
} catch (Exception e) {
log.error("包裝請(qǐng)求失敗", e);
return false;
}
}
/**
* 自定義請(qǐng)求包裝類,用于替換請(qǐng)求體內(nèi)容
*/
private static class DecryptedRequestWrapper extends HttpServletRequestWrapper {
private final String decryptedData;
public DecryptedRequestWrapper(HttpServletRequest request, String decryptedData) {
super(request);
this.decryptedData = decryptedData;
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(decryptedData.getBytes(StandardCharsets.UTF_8))));
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
decryptedData.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
}
}
現(xiàn)在還需要補(bǔ)充缺少的導(dǎo)入包,以及添加解密注解:
package com.example.secureapi.annotation;
import java.lang.annotation.*;
/**
* 標(biāo)記需要解密處理的控制器或方法
* 可以應(yīng)用于類或方法級(jí)別
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
// 可以添加額外配置參數(shù),例如是否檢查時(shí)間戳、是否驗(yàn)證簽名等
boolean checkTimestamp() default true;
boolean verifySignature() default false;
}
最后,我們還需要在 Spring Boot 配置中注冊(cè)這個(gè)攔截器:
package com.example.secureapi.config;
import com.example.secureapi.interceptor.DecryptInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private DecryptInterceptor decryptInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加解密攔截器,應(yīng)用到所有API請(qǐng)求路徑
registry.addInterceptor(decryptInterceptor)
.addPathPatterns("/api/**");
}
}
總結(jié)
這篇文章詳細(xì)介紹了如何在 SpringBoot 應(yīng)用中實(shí)現(xiàn) RSA+AES 混合加密方案,為接口通信提供安全保障。文章首先解釋了接口加密的必要性,指出未加密的網(wǎng)絡(luò)數(shù)據(jù)容易被抓包工具獲取,特別是當(dāng)傳輸敏感信息時(shí)風(fēng)險(xiǎn)更大。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)RSA+AES自動(dòng)接口解密的文章就介紹到這了,更多相關(guān)SpringBoot RSA+AES自動(dòng)解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java常用正則表達(dá)式驗(yàn)證工具類RegexUtils.java
相信大家對(duì)正則表達(dá)式一定都有所了解和研究,這篇文章主要為大家分享了Java 表單注冊(cè)常用正則表達(dá)式驗(yàn)證工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法
這篇文章主要介紹了在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法,實(shí)現(xiàn)代碼為Java語(yǔ)言編寫,是需要的朋友可以參考下2015-07-07
使用java web 在jsp文件及Class中連接MySQL和SQLserver 的驅(qū)動(dòng)方法
這篇文章主要介紹了使用java web 在jsp文件及Class中連接MySQL和SQLserver的驅(qū)動(dòng)方法的相關(guān)資料,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
spring aop底層原理及如何實(shí)現(xiàn)
這篇文章主要介紹了spring aop底層原理及如何實(shí)現(xiàn),幫助大家更好的理解和學(xué)習(xí)使用spring aop,感興趣的朋友可以了解下2021-04-04
Java使用Spire.PDF實(shí)現(xiàn)高效壓縮PDF文件
這篇文章主要介紹了過(guò)大的文件體積常引發(fā)傳輸卡頓、加載緩慢等問(wèn)題,本文將解析?Spire.PDF?for?Java?的?PDF?壓縮方案,有需要的可以了解下2025-08-08
開(kāi)發(fā)10年,全記在這本Java進(jìn)階寶典里了
這篇文章主要給大家分享介紹了這本Java進(jìn)階寶典里,是開(kāi)發(fā)10年總結(jié)出來(lái)的,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧2019-04-04

