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 中添加必要的依赖:

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>

加密工具类

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

java 复制代码
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;
    }
}

自定义请求包装类

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

typescript 复制代码
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 解密拦截器

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

java 复制代码
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();
                }
            };
        }
    }
}

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

java 复制代码
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 配置中注册这个拦截器:

kotlin 复制代码
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 混合加密方案,为接口通信提供安全保障。文章首先解释了接口加密的必要性,指出未加密的网络数据容易被抓包工具获取,特别是当传输敏感信息时风险更大。

公众号:trymoLiu 欢迎前来关注~

相关推荐
小杨40412 分钟前
springboot框架项目实践应用三(监控运维组件admin)
spring boot·后端·监控
Y雨何时停T25 分钟前
Spring IoC 详解
java·spring·rpc
&白帝&41 分钟前
Java @PathVariable获取路径参数
java·开发语言·python
Yuanymoon1 小时前
【由技及道】镜像星门开启:Harbor镜像推送的量子跃迁艺术【人工智障AI2077的开发日志010】
java·docker·jenkins·harbor·devops
木胭脂沾染了灰1 小时前
策略设计模式-下单
java·前端·设计模式
sevevty-seven2 小时前
Spring Boot 自动装配原理详解
java·spring boot·后端
Forget the Dream3 小时前
设计模式之迭代器模式
java·c++·设计模式·迭代器模式
MiniFlyZt3 小时前
消息队列MQ(RabbitMQ)
spring boot·分布式·微服务·rabbitmq
大丈夫在世当日食一鲲3 小时前
Java中用到的设计模式
java·开发语言·设计模式
A-Kamen3 小时前
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
java·spring boot·后端