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

引言

最近项目中遇到一个安全需求:前端传输敏感信息(比如用户身份证、银行卡号)时,不能明文传输。如果只靠HTTPS还不够保险,需要在应用层面再加一层加密。

很多同学可能只知道用HTTPS加密传输,但其实面对更严格的安全要求,我们需要在应用层实现数据加密。今天就来聊聊如何用RSA+AES混合加密方案,实现接口的自动加解密。

为什么要用RSA+AES混合加密?

单一加密算法的局限

RSA的问题

  • 加密速度慢,只适合加密少量数据
  • 数据长度受限,2048位密钥最多加密245字节
  • 性能开销大,不适合频繁的大数据加密

AES的问题

  • 对称加密,密钥分发困难
  • 一旦密钥泄露,所有数据都不安全
  • 安全性依赖于密钥的保密性

混合加密的优势

我们把两种算法结合起来:

  • 用RSA加密AES密钥(密钥协商)
  • 用AES加密实际数据(数据传输)

这样既解决了RSA性能问题,又解决了AES密钥分发问题。

核心概念解析

加密流程

前端 → 生成随机AES密钥 → 用RSA公钥加密AES密钥
     → 用AES密钥加密数据 → 发送{加密AES密钥 + 加密数据}到后端

后端 → 用RSA私钥解密得到AES密钥 → 用AES密钥解密数据

安全保障机制

  • 密钥协商:每次请求生成新的AES密钥
  • 防重放攻击:加入时间戳验证
  • 数据完整性:可选的数字签名

整体架构设计

我们的自动解密架构:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   前端请求      │───▶│   加密请求体     │───▶│  SpringBoot     │
│  (原始数据)     │    │  (AES密钥+数据)  │    │   拦截器        │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                    │
                                    ┌───────────────┼───────────────┐
                                    ▼               ▼               ▼
                            ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
                            │ RSA解密     │  │ AES解密     │  │ 参数绑定     │
                            │ (AES密钥)   │  │ (业务数据)  │  │ (反射注入)  │
                            └─────────────┘  └─────────────┘  └─────────────┘

核心设计要点

1. 加密请求体设计

// 加密请求体结构
@Data
public class EncryptedRequest {
    private String encryptedKey;  // RSA加密的AES密钥
    private String iv;           // 初始化向量
    private String encryptedData; // AES加密的业务数据
    private Long timestamp;      // 时间戳,防重放
    private String signature;    // 数字签名,可选
}

2. 自动解密注解

// 标记需要自动解密的接口
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoDecrypt {
    boolean required() default true;  // 是否必须解密
    String[] excludeFields() default {}; // 不参与解密的字段
}

3. 配置管理

security:
  crypto:
    rsa:
      # 服务端私钥(存储在配置中心或环境变量)
      private-key: "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD..."
      # 前端公钥(用于验证前端身份)
      frontend-public-key: "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOx..."
    aes:
      # AES算法配置
      algorithm: "AES/CBC/PKCS5Padding"
      key-length: 256
      iv-length: 16
    # 防重放窗口(毫秒)
    replay-window: 300000  # 5分钟

关键实现细节

1. 加密工具类

@Component
public class CryptoUtils {
    
    // AES加密
    public static String aesEncrypt(String data, String key, String iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));
        
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    // AES解密
    public static String aesDecrypt(String encryptedData, String key, String iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));
        
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decrypted, StandardCharsets.UTF_8);
    }
    
    // RSA解密AES密钥
    public static String rsaDecrypt(String encryptedKey, String privateKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedKey));
        return Base64.getEncoder().encodeToString(decrypted);
    }
}

2. 解密拦截器

@Component
@Order(1)  // 确保在参数绑定之前执行
public class AutoDecryptInterceptor implements HandlerInterceptor {
    
    @Autowired
    private CryptoUtils cryptoUtils;
    
    @Value("${security.crypto.rsa.private-key}")
    private String rsaPrivateKey;
    
    @Value("${security.crypto.replay-window}")
    private long replayWindow;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        AutoDecrypt autoDecrypt = handlerMethod.getMethodAnnotation(AutoDecrypt.class);
        
        if (autoDecrypt == null) {
            return true;  // 没有注解,跳过解密
        }
        
        // 读取请求体
        String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
        
        if (StringUtils.isBlank(body)) {
            return true;
        }
        
        try {
            // 解析加密请求体
            EncryptedRequest encryptedReq = JSON.parseObject(body, EncryptedRequest.class);
            
            // 防重放验证
            if (!validateTimestamp(encryptedReq.getTimestamp())) {
                throw new RuntimeException("请求已过期或重复");
            }
            
            // RSA解密AES密钥
            String aesKey = cryptoUtils.rsaDecrypt(encryptedReq.getEncryptedKey(), rsaPrivateKey);
            
            // AES解密业务数据
            String decryptedData = cryptoUtils.aesDecrypt(encryptedReq.getEncryptedData(), aesKey, encryptedReq.getIv());
            
            // 替换请求体,让后续处理器处理解密后的数据
            ServletRequestWrapper wrappedRequest = new DecryptedRequestWrapper(request, decryptedData);
            request.setAttribute("wrappedRequest", wrappedRequest);
            
        } catch (Exception e) {
            throw new RuntimeException("解密失败: " + e.getMessage(), e);
        }
        
        return true;
    }
    
    private boolean validateTimestamp(Long timestamp) {
        if (timestamp == null) {
            return false;
        }
        long currentTime = System.currentTimeMillis();
        return Math.abs(currentTime - timestamp) <= replayWindow;
    }
}

3. 请求体包装器

public class DecryptedRequestWrapper extends HttpServletRequestWrapper {
    
    private final String decryptedBody;
    
    public DecryptedRequestWrapper(HttpServletRequest request, String decryptedBody) {
        super(request);
        this.decryptedBody = decryptedBody;
    }
    
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedBody.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) {
                // 不需要实现
            }
            
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }
    
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

4. 控制器使用示例

@RestController
@RequestMapping("/api")
public class UserController {
    
    @PostMapping("/user/create")
    @AutoDecrypt  // 标记此接口需要自动解密
    public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
        // 这里的request已经是解密后的数据
        User user = userService.createUser(request);
        return ResponseEntity.ok(user);
    }
    
    @PostMapping("/user/update")
    @AutoDecrypt(excludeFields = {"id"})  // 除id外的所有字段都要解密
    public ResponseEntity<User> updateUser(@RequestBody UpdateUserRequest request) {
        User user = userService.updateUser(request);
        return ResponseEntity.ok(user);
    }
}

业务场景应用

1. 用户敏感信息保护

// 用户创建请求(包含敏感信息)
@Data
public class CreateUserRequest {
    private String name;        // 姓名
    private String idCard;      // 身份证号(加密)
    private String phone;       // 手机号(加密)
    private String bankCard;    // 银行卡号(加密)
    private String address;     // 地址(加密)
}

2. 支付信息加密

// 支付请求
@Data
public class PaymentRequest {
    private String orderId;     // 订单号
    private String cardNumber;  // 卡号(加密)
    private String cvv;         // CVV(加密)
    private String expiryDate;  // 有效期(加密)
    private BigDecimal amount;  // 金额
}

3. 文件上传加密

@PostMapping("/upload")
@AutoDecrypt
public ResponseEntity<String> uploadFile(@RequestBody FileUploadRequest request) {
    // 文件内容在request.fileData中,已解密
    String fileId = fileService.saveEncryptedFile(request.getFileData());
    return ResponseEntity.ok(fileId);
}

最佳实践建议

1. 密钥安全管理

@Configuration
public class KeyManagementConfig {
    
    // 从配置中心获取密钥
    @Bean
    public String rsaPrivateKey(@Value("${security.crypto.rsa.private-key-encrypted}") String encryptedKey) {
        // 解密存储的密钥
        return decryptStoredKey(encryptedKey);
    }
    
    // 定期轮换密钥
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点执行
    public void rotateKeys() {
        // 生成新密钥对
        KeyPair newKeyPair = generateRSAKeyPair();
        
        // 更新密钥存储
        updateKeyStorage(newKeyPair);
        
        // 通知前端更新公钥
        notifyFrontendToUpdatePublicKey(newKeyPair.getPublic());
    }
}

2. 性能优化

@Component
public class CryptoCacheManager {
    
    // 缓存AES密钥,避免重复解密
    private final LoadingCache<String, String> aesKeyCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(Duration.ofMinutes(5))
        .build(this::decryptAESKey);
    
    public String getCachedAESKey(String encryptedKey) {
        return aesKeyCache.get(encryptedKey);
    }
    
    private String decryptAESKey(String encryptedKey) {
        try {
            return CryptoUtils.rsaDecrypt(encryptedKey, rsaPrivateKey);
        } catch (Exception e) {
            throw new RuntimeException("AES密钥解密失败", e);
        }
    }
}

3. 监控告警

@Component
@Slf4j
public class CryptoMonitor {
    
    @EventListener
    public void handleCryptoEvent(CryptoOperationEvent event) {
        if (event.isSuccess()) {
            log.info("加密操作成功: type={}, duration={}ms", 
                event.getType(), event.getDuration());
        } else {
            log.error("加密操作失败: type={}, error={}", 
                event.getType(), event.getError());
        }
        
        // 上报监控指标
        Metrics.counter("crypto.operation", 
            "type", event.getType(),
            "result", event.isSuccess() ? "success" : "failure")
            .increment();
    }
}

预期效果

通过RSA+AES混合加密方案,我们可以实现:

  • 数据安全:敏感信息传输全程加密
  • 性能保障:AES加密速度快,不影响用户体验
  • 易于维护:自动解密,业务代码无感知
  • 合规要求:满足数据安全法规要求
  • 成本可控:利用现有SpringBoot框架,无需额外组件

这套方案既能满足严格的安全要求,又保持了良好的性能和开发体验,是企业级应用的理想选择。

希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。


标题:SpringBoot 实现 RSA+AES 自动接口解密实战
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/02/11/1770614590862.html

    评论
    0 评论
avatar

取消