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

 更新时间:2025年10月22日 15:04:01   作者:trymoLiu  
在现代应用开发中,接口安全性变得越来越重要,本文将详细介绍如何在 SpringBoot 应用中实现 RSA+AES 混合加密方案,具有一定的参考价值,感兴趣的可以了解一下

引言

在现代应用开发中,接口安全性变得越来越重要。当敏感数据通过网络传输时,如何确保数据不被窃取或篡改?本文将详细介绍如何在 SpringBoot 应用中实现 RSA+AES 混合加密方案,为接口通信提供强大的安全保障。

为什么需要接口加密?

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

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

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

  1. RSA: 非对称加密,安全性高,但加密速度较慢,适合加密少量数据
  2. 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;

    @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) {
                // 读取请求体内容
                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密钥解密实际数据
                String decryptedData = EncryptionUtils.decryptWithAES(encryptedRequest.getEncryptedData(), aesKey, iv);
                
                // 防重放攻击检查(可选)
                if (encryptedRequest.getTimestamp() != null) {
                    long currentTime = System.currentTimeMillis();
                    // 如果请求时间戳与当前时间相差超过5分钟,则拒绝请求
                    if (Math.abs(currentTime - encryptedRequest.getTimestamp()) > 300000) {
                        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                        response.getWriter().write("{"code":403,"message":"请求已过期"}");
                        return false;
                    }
                }
                
                log.debug("解密后的数据: {}", decryptedData);
                
                // 创建一个包含解密数据的新BufferedReader,替换原有的请求Reader
                request.setAttribute("DECRYPTED_DATA", decryptedData);
                
                // 包装请求,使控制器能够读取解密后的数据
                return wrapRequest(request, decryptedData);
            }
        }
        return true;
    }
    
    /**
     * 包装HttpServletRequest,替换请求体内容为解密后的数据
     * @param request 原始请求
     * @param decryptedData 解密后的数据
     * @return 是否成功包装请求
     */
    private boolean wrapRequest(HttpServletRequest request, String decryptedData) {
        try {
            // 创建包装后的请求对象
            DecryptedRequestWrapper wrapper = new DecryptedRequestWrapper(request, decryptedData);
            // 替换当前请求
            request.setAttribute("org.springframework.web.util.WebUtils.ERROR_EXCEPTION_ATTRIBUTE", wrapper);
            return true;
        } catch (Exception e) {
            log.error("包装请求失败", e);
            return false;
        }
    }
    
    /**
     * 自定义请求包装类,用于替换请求体内容
     */
    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();
                }
            };
        }
    }
}

现在还需要补充缺少的导入包,以及添加解密注解:

package com.example.secureapi.annotation;

import java.lang.annotation.*;

/**
 * 标记需要解密处理的控制器或方法
 * 可以应用于类或方法级别
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
    // 可以添加额外配置参数,例如是否检查时间戳、是否验证签名等
    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) {
        // 添加解密拦截器,应用到所有API请求路径
        registry.addInterceptor(decryptInterceptor)
                .addPathPatterns("/api/**");
    }
}

总结

这篇文章详细介绍了如何在 SpringBoot 应用中实现 RSA+AES 混合加密方案,为接口通信提供安全保障。文章首先解释了接口加密的必要性,指出未加密的网络数据容易被抓包工具获取,特别是当传输敏感信息时风险更大。

到此这篇关于SpringBoot实现RSA+AES自动接口解密的文章就介绍到这了,更多相关SpringBoot RSA+AES自动解密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决java调用python代码返回值中文乱码问题

    解决java调用python代码返回值中文乱码问题

    这篇文章主要介绍了解决java调用python代码返回值中文乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Java中各类日期和时间转换超详析总结(Date和LocalDateTime相互转换等)

    Java中各类日期和时间转换超详析总结(Date和LocalDateTime相互转换等)

    这篇文章主要介绍了Java中日期和时间处理的几个阶段,包括java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp、java.util.Calendar和java.util.GregorianCalendar等类,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-01-01
  • SpringBoot项目自定义静态资源映射规则的实现代码

    SpringBoot项目自定义静态资源映射规则的实现代码

    在开发Web应用时,我们经常需要处理文件上传与访问,​传统做法可能是使用Nginx反向代理,但对于小型项目或快速开发场景,我们可以直接用​Spring MVC的静态资源映射​ 功能,本文将基于WebMvcConfigurer手写配置,实现 ​本地文件目录映射为Web URL,需要的朋友可以参考下
    2025-08-08
  • 基于Java实现Socket编程入门

    基于Java实现Socket编程入门

    Java最初是作为网络编程语言出现的,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket,本文就来介绍一下基于Java实现Socket编程入门,感兴趣的可以来了解一下
    2022-03-03
  • Spring Cloud-Feign服务调用的问题及处理方法

    Spring Cloud-Feign服务调用的问题及处理方法

    Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。接下来通过本文给大家介绍Spring Cloud-Feign服务调用,需要的朋友可以参考下
    2021-10-10
  • 解决idea导入maven项目缺少jar包的问题方法

    解决idea导入maven项目缺少jar包的问题方法

    这篇文章主要介绍了解决idea导入maven项目缺少jar包的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • java工具类StringUtils使用实例详解

    java工具类StringUtils使用实例详解

    这篇文章主要为大家介绍了java工具类StringUtils使用实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Java元组类型javatuples使用实例

    Java元组类型javatuples使用实例

    这篇文章主要介绍了Java元组类型javatuples使用实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java显示程序包不存在的三种解决方法总结

    Java显示程序包不存在的三种解决方法总结

    在Java开发中,有时会遇到“程序包javax.servlet不存在”等错误提示,这通常是因为缺少必要的库或依赖项,这篇文章主要给大家介绍了关于Java显示程序包不存在的三种解决方法,需要的朋友可以参考下
    2024-07-07
  • SpringMVC实现用户登录全过程

    SpringMVC实现用户登录全过程

    这篇文章主要介绍了SpringMVC实现用户登录全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-09-09

最新评论