SpringBoot 实现 RSA+AES 自动接口解密

embedded/2025/3/13 5:15:32/

为什么需要接口加密?

在没有加密的情况下,通过网络传输的数据可以被抓包工具轻松获取。特别是当传输包含用户隐私、支付信息等敏感数据时,这种风险更加不可接受。接口加密能够确保即使数据被截获,攻击者也无法理解其中的内容。

RSA+AES 混合加密方案的优势

我们选择 RSA+AES 混合加密方案是因为它结合了两种算法的优点:

RSA: 非对称加密,安全性高,但加密速度较慢,适合加密少量数据
AES: 对称加密,加密速度快,适合大量数据加密,但密钥分发是个难题
通过混合使用这两种算法,我们用 RSA 来加密 AES 的密钥,然后用 AES 来加密实际传输的数据,既保证了安全性,又兼顾了性能。

实现原理

1、客户端与服务端预先约定 RSA 公钥和私钥
2、客户端随机生成 AES 密钥,使用 RSA 公钥加密这个 AES 密钥
3、使用 AES 密钥加密实际请求数据
4、将加密后的 AES 密钥和加密后的数据一起发送给服务端
5、服务端先用 RSA 私钥解密得到 AES 密钥,再用 AES 密钥解密数据
下面我们将通过实例代码演示如何在 SpringBoot 中实现这一方案。

项目依赖
首先在 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>

加密工具类
下面是我们需要实现的加密工具类:

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 生成过程中可能出现的异常*/public static KeyPair generateRSAKeyPair() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(RSA_KEY_SIZE);return keyPairGenerator.generateKeyPair();}/*** 将密钥转换为Base64编码字符串,便于存储和传输* @param key 密钥* @return Base64编码的密钥字符串*/public static String keyToString(Key key) {return Base64.getEncoder().encodeToString(key.getEncoded());}/*** 从Base64编码的字符串恢复RSA公钥* @param keyStr Base64编码的公钥字符串* @return RSA公钥对象* @throws Exception 转换过程中可能出现的异常*/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编码的字符串恢复RSA私钥* @param keyStr Base64编码的私钥字符串* @return RSA私钥对象* @throws Exception 转换过程中可能出现的异常*/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 生成过程中可能出现的异常*/public static SecretKey generateAESKey() throws Exception {KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(AES_KEY_SIZE);return keyGen.generateKey();}/*** 从Base64编码的字符串恢复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公钥加密数据* @param data 待加密数据* @param publicKey RSA公钥* @return Base64编码的加密数据* @throws Exception 加密过程中可能出现的异常*/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私钥解密数据* @param encryptedData Base64编码的加密数据* @param privateKey RSA私钥* @return 解密后的原始数据* @throws Exception 解密过程中可能出现的异常*/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密钥加密数据* @param data 待加密数据* @param secretKey AES密钥* @param iv 初始化向量* @return Base64编码的加密数据* @throws Exception 加密过程中可能出现的异常*/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密钥解密数据* @param encryptedData Base64编码的加密数据* @param secretKey AES密钥* @param iv 初始化向量* @return 解密后的原始数据* @throws Exception 解密过程中可能出现的异常*/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字节的随机初始化向量*/public static byte[] generateIV() {SecureRandom random = new SecureRandom();byte[] iv = new byte[16];  // AES使用16字节的初始化向量random.nextBytes(iv);return iv;}
}

自定义请求包装类
为了实现加密请求的自动解密,我们需要定义一个请求包装类:

package com.example.secureapi.model;import lombok.Data;@Data
public class EncryptedRequest {// 使用RSA加密后的AES密钥private String encryptedKey;// 使用Base64编码的AES初始化向量private String iv;// 使用AES加密后的业务数据private String encryptedData;// 可选:时间戳,用于防重放攻击private Long timestamp;// 可选:签名,用于验证请求完整性private String signature;
}

Spring Boot 解密拦截器
我们接下来实现一个请求解密拦截器,它会在控制器处理请求之前自动解密数据:

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;@Overridepublic 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) {// 读取请求体内容String requestBody = request.getReader().lines().collect(Collectors.joining());// 解析加密的请求对象EncryptedRequest encryptedRequest = JSON.parseObject(requestBody, EncryptedRequest.class);// 获取RSA私钥PrivateKey rsaPrivateKey = EncryptionUtils.stringToRSAPrivateKey(rsaPrivateKeyStr);// 解密A

http://www.ppmy.cn/embedded/172156.html

相关文章

一周热点-OpenAI 推出了 GPT-4.5,这可能是其最后一个非推理模型

在人工智能领域,大型语言模型一直是研究的热点。OpenAI 的 GPT 系列模型在自然语言处理方面取得了显著成就。GPT-4.5 是 OpenAI 在这一领域的又一力作,它在多个方面进行了升级和优化。 1 新模型的出现 GPT-4.5 目前作为研究预览版发布。与 OpenAI 最近的 o1 和 o3 模型不同,…

篮球游戏(200分)

(200分)113.篮球游戏 篮球游戏 问题描述 幼儿园里有一个放倒的圆桶,它是一个线性结构,允许在桶的右边将篮球放入,可以在桶的左边和右边将篮球取出。每个篮球有单独的编号。老师可以连续放入一个或多个篮球,小朋友可以在桶左边或右边将篮球取出。当桶只有一个篮球时,必须…

RedisDesktopManager连接不上redis数据库的解决方法

RedisDesktopManager是一款连接redis数据库的客户端。 第一步 在自己的本机外面试下能不能连接上虚拟机&#xff0c;打开cmd&#xff0c;使用 ping 192.168.65.128 &#xff08;192.168.65.128这个为虚拟机的ip地址 查看虚拟机的ip地址&#xff1a;命令 ifconfig&#xff09; 如…

使用SSH密钥连接本地git 和 github

目录 配置本地SSH&#xff0c;添加到github首先查看本地是否有SSH密钥生成SSH密钥&#xff0c;和邮箱绑定将 SSH 密钥添加到 ssh-agent&#xff1a;显示本地公钥*把下面这一串生成的公钥存到github上* 验证SSH配置是否成功终端跳转到本地仓库把http协议改为SSH&#xff08;如果…

vscode带参数调试

转载&#xff1a;【深度学习环境】VSCode 调试python 带args参数的程序_vscode args-CSDN博客 ctrlshiftf全局搜索

贪心算法解题框架+经典反例分析,效率提升300%

贪心算法是一种在每一步选择中都采取当前状态下的最优决策&#xff0c;从而希望最终达到全局最优解的算法策略。以下从其定义、特点、一般步骤、应用场景及实例等方面进行讲解&#xff1a; 定义与基本思想 • 贪心算法在对问题求解时&#xff0c;总是做出在当前看来是最好的选…

Android WebSocket工具类:重连、心跳、消息队列一站式解决方案

依赖库 使用 OkHttp 的WebSocket支持。 在 build.gradle 中添加依赖&#xff1a; implementation com.squareup.okhttp3:okhttp:4.9.3WebSocket工具类实现 import okhttp3.*; import android.os.Handler; import android.os.Looper; import android.util.Log;import java.ut…

系统架构设计师—系统架构设计篇—轻量级架构

文章目录 基本概念轻量级架构持久层的优点 SSHSSMHibernate与Mybatis的区别 基本概念 轻量级架构 J2EE环境下&#xff0c;分层架构&#xff1a; 表现层业务逻辑层持久层 持久层的优点 屏蔽数据库平台的变化对业务逻辑层的影响。通过持久层的封装处理&#xff0c;可以在持久…