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 评论