SpringBoot實現(xiàn)RSA+AES自動接口解密
引言
在現(xiàn)代應(yīng)用開發(fā)中,接口安全性變得越來越重要。當(dāng)敏感數(shù)據(jù)通過網(wǎng)絡(luò)傳輸時,如何確保數(shù)據(jù)不被竊取或篡改?本文將詳細(xì)介紹如何在 SpringBoot 應(yīng)用中實現(xiàn) RSA+AES 混合加密方案,為接口通信提供強大的安全保障。
為什么需要接口加密?
在沒有加密的情況下,通過網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)可以被抓包工具輕松獲取。特別是當(dāng)傳輸包含用戶隱私、支付信息等敏感數(shù)據(jù)時,這種風(fēng)險更加不可接受。接口加密能夠確保即使數(shù)據(jù)被截獲,攻擊者也無法理解其中的內(nèi)容。
RSA+AES 混合加密方案的優(yōu)勢
我們選擇 RSA+AES 混合加密方案是因為它結(jié)合了兩種算法的優(yōu)點:
- RSA: 非對稱加密,安全性高,但加密速度較慢,適合加密少量數(shù)據(jù)
- AES: 對稱加密,加密速度快,適合大量數(shù)據(jù)加密,但密鑰分發(fā)是個難題
通過混合使用這兩種算法,我們用 RSA 來加密 AES 的密鑰,然后用 AES 來加密實際傳輸?shù)臄?shù)據(jù),既保證了安全性,又兼顧了性能。
實現(xiàn)原理
- 客戶端與服務(wù)端預(yù)先約定 RSA 公鑰和私鑰
- 客戶端隨機生成 AES 密鑰,使用 RSA 公鑰加密這個 AES 密鑰
- 使用 AES 密鑰加密實際請求數(shù)據(jù)
- 將加密后的 AES 密鑰和加密后的數(shù)據(jù)一起發(fā)送給服務(wù)端
- 服務(wù)端先用 RSA 私鑰解密得到 AES 密鑰,再用 AES 密鑰解密數(shù)據(jù)
下面我們將通過實例代碼演示如何在 SpringBoot 中實現(xiàn)這一方案。
項目依賴
首先在 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>
加密工具類
下面是我們需要實現(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作為安全提供者,提供更強大的加密算法支持
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位密鑰提供更強的安全性
// RSA加密配置
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 使用PKCS1填充
private static final int RSA_KEY_SIZE = 2048; // 使用2048位密鑰長度,提供足夠的安全強度
/**
* 生成RSA密鑰對
* @return 包含公鑰和私鑰的密鑰對
* @throws Exception 生成過程中可能出現(xiàn)的異常
*/
public static KeyPair generateRSAKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(RSA_KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
/**
* 將密鑰轉(zhuǎn)換為Base64編碼字符串,便于存儲和傳輸
* @param key 密鑰
* @return Base64編碼的密鑰字符串
*/
public static String keyToString(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 從Base64編碼的字符串恢復(fù)RSA公鑰
* @param keyStr Base64編碼的公鑰字符串
* @return RSA公鑰對象
* @throws Exception 轉(zhuǎn)換過程中可能出現(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私鑰對象
* @throws Exception 轉(zhuǎn)換過程中可能出現(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);
}
/**
* 生成隨機AES密鑰
* @return AES密鑰
* @throws Exception 生成過程中可能出現(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密鑰對象
*/
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 加密過程中可能出現(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 解密過程中可能出現(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 加密過程中可能出現(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 解密過程中可能出現(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);
}
/**
* 生成隨機初始化向量
* @return 16字節(jié)的隨機初始化向量
*/
public static byte[] generateIV() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16]; // AES使用16字節(jié)的初始化向量
random.nextBytes(iv);
return iv;
}
}
自定義請求包裝類
為了實現(xiàn)加密請求的自動解密,我們需要定義一個請求包裝類:
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;
// 可選:時間戳,用于防重放攻擊
private Long timestamp;
// 可選:簽名,用于驗證請求完整性
private String signature;
}
Spring Boot 解密攔截器
我們接下來實現(xiàn)一個請求解密攔截器,它會在控制器處理請求之前自動解密數(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注解,進行解密處理
if (decryptAnnotation != null) {
// 讀取請求體內(nèi)容
String requestBody = request.getReader().lines().collect(Collectors.joining());
// 解析加密的請求對象
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ù)據(jù)
String decryptedData = EncryptionUtils.decryptWithAES(encryptedRequest.getEncryptedData(), aesKey, iv);
// 防重放攻擊檢查(可選)
if (encryptedRequest.getTimestamp() != null) {
long currentTime = System.currentTimeMillis();
// 如果請求時間戳與當(dāng)前時間相差超過5分鐘,則拒絕請求
if (Math.abs(currentTime - encryptedRequest.getTimestamp()) > 300000) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{"code":403,"message":"請求已過期"}");
return false;
}
}
log.debug("解密后的數(shù)據(jù): {}", decryptedData);
// 創(chuàng)建一個包含解密數(shù)據(jù)的新BufferedReader,替換原有的請求Reader
request.setAttribute("DECRYPTED_DATA", decryptedData);
// 包裝請求,使控制器能夠讀取解密后的數(shù)據(jù)
return wrapRequest(request, decryptedData);
}
}
return true;
}
/**
* 包裝HttpServletRequest,替換請求體內(nèi)容為解密后的數(shù)據(jù)
* @param request 原始請求
* @param decryptedData 解密后的數(shù)據(jù)
* @return 是否成功包裝請求
*/
private boolean wrapRequest(HttpServletRequest request, String decryptedData) {
try {
// 創(chuàng)建包裝后的請求對象
DecryptedRequestWrapper wrapper = new DecryptedRequestWrapper(request, decryptedData);
// 替換當(dāng)前請求
request.setAttribute("org.springframework.web.util.WebUtils.ERROR_EXCEPTION_ATTRIBUTE", wrapper);
return true;
} catch (Exception e) {
log.error("包裝請求失敗", e);
return false;
}
}
/**
* 自定義請求包裝類,用于替換請求體內(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)在還需要補充缺少的導(dǎo)入包,以及添加解密注解:
package com.example.secureapi.annotation;
import java.lang.annotation.*;
/**
* 標(biāo)記需要解密處理的控制器或方法
* 可以應(yīng)用于類或方法級別
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
// 可以添加額外配置參數(shù),例如是否檢查時間戳、是否驗證簽名等
boolean checkTimestamp() default true;
boolean verifySignature() default false;
}
最后,我們還需要在 Spring Boot 配置中注冊這個攔截器:
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請求路徑
registry.addInterceptor(decryptInterceptor)
.addPathPatterns("/api/**");
}
}
總結(jié)
這篇文章詳細(xì)介紹了如何在 SpringBoot 應(yīng)用中實現(xiàn) RSA+AES 混合加密方案,為接口通信提供安全保障。文章首先解釋了接口加密的必要性,指出未加密的網(wǎng)絡(luò)數(shù)據(jù)容易被抓包工具獲取,特別是當(dāng)傳輸敏感信息時風(fēng)險更大。
到此這篇關(guān)于SpringBoot實現(xiàn)RSA+AES自動接口解密的文章就介紹到這了,更多相關(guān)SpringBoot RSA+AES自動解密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決java調(diào)用python代碼返回值中文亂碼問題
這篇文章主要介紹了解決java調(diào)用python代碼返回值中文亂碼問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
Java中各類日期和時間轉(zhuǎn)換超詳析總結(jié)(Date和LocalDateTime相互轉(zhuǎn)換等)
這篇文章主要介紹了Java中日期和時間處理的幾個階段,包括java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp、java.util.Calendar和java.util.GregorianCalendar等類,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01
SpringBoot項目自定義靜態(tài)資源映射規(guī)則的實現(xiàn)代碼
在開發(fā)Web應(yīng)用時,我們經(jīng)常需要處理文件上傳與訪問,?傳統(tǒng)做法可能是使用Nginx反向代理,但對于小型項目或快速開發(fā)場景,我們可以直接用?Spring MVC的靜態(tài)資源映射? 功能,本文將基于WebMvcConfigurer手寫配置,實現(xiàn) ?本地文件目錄映射為Web URL,需要的朋友可以參考下2025-08-08
Spring Cloud-Feign服務(wù)調(diào)用的問題及處理方法
Feign 是一個聲明式的 REST 客戶端,它用了基于接口的注解方式,很方便實現(xiàn)客戶端配置。接下來通過本文給大家介紹Spring Cloud-Feign服務(wù)調(diào)用,需要的朋友可以參考下2021-10-10
解決idea導(dǎo)入maven項目缺少jar包的問題方法
這篇文章主要介紹了解決idea導(dǎo)入maven項目缺少jar包的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

