Spring Boot 实战:RSA+AES 接口全链路加解密(防篡改 / 防重放)
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页 @怒放吧德德 To记录领地 @一个有梦有戏的人
🌝分享学习心得,欢迎指正,大家一起学习成长!
转发请携带作者信息 @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)

前言
本项目演示了如何在 Spring Boot 应用中实现安全的 API 接口加密传输机制,采用 RSA + AES-GCM 混合加密方案,确保敏感数据在传输过程中的机密性和完整性。基于 Spring Boot 的 API 接口加密传输解决方案,实现前后端数据的端到端加密通信。
1 方案背景:为什么HTTPS之外还要做接口加密?
很多开发者会有疑问:我已经用了HTTPS,为什么还要做接口级加解密?
核心答案是:HTTPS只解决了传输链路的安全,无法覆盖「终端被Hook、App被逆向、API网关日志泄露、内网流量被侧录」等场景的风险。
HTTPS是"防路上被偷看",而接口级加密是"防终点被看穿",两者解决的问题完全不同,具体对比如下:
| 风险场景 | HTTPS | 接口级加密 |
|---|---|---|
| 传输链路被监听 | ✅ 防护 | ✅ 防护 |
| 终端被Hook/抓包工具解密 | ❌ 无法防护 | ✅ 防护 |
| App被逆向、API逻辑被破解 | ❌ 无法防护 | ✅ 防护 |
| 网关/代理日志明文泄露 | ❌ 无法防护 | ✅ 防护 |
| 内网不可信链路流量被侧录 | ❌ 无法防护 | ✅ 防护 |
2 整体方案设计
核心特性:
- 混合加密方案:RSA-OAEP 加密 AES 密钥,AES-GCM 加密业务数据
- 防重放攻击:基于时间戳 + Nonce 的双重防护机制
- 数据完整性校验:AES-GCM 模式自带认证标签,防止数据篡改
- 注解式开发:通过 @DecryptRequestBody 和 @EncryptResponseBody 注解实现透明加解密
- 前后端兼容:与 Web Crypto API 完全兼容,支持浏览器原生加密
2.1 核心设计目标
| 核心目标 | 落地方案 |
|---|---|
| 密钥传输安全 | RSA-OAEP 非对称加密(兼容WebCrypto,规避老旧填充方式的安全风险) |
| 业务数据加解密 | AES-256-GCM 对称加密(自带完整性校验,CPU指令集加速,支持AAD防篡改) |
| 防请求篡改 | GCM-AAD 附加认证数据(timestamp+nonce参与认证,篡改直接解密失败) |
| 防请求重放 | timestamp时间窗口校验 + nonce唯一值幂等控制(Redis实现) |
| 业务零侵入 | 自定义注解 + 参数解析器 + 响应增强,Controller无需感知加解密逻辑 |
| 跨端兼容性 | 前后端算法100%对齐,支持Web/小程序/App多端对接 |
| 生产可运维 | 支持密钥平滑轮换、统一异常处理、全链路日志追踪 |
2.2 全链路时序流程
- 客户端提前请求服务端,获取RSA公钥
- 客户端生成随机AES-256密钥(一请求一密钥,杜绝密钥泄露风险)
- 客户端用AES-GCM加密业务请求数据,将timestamp+nonce作为AAD附加认证数据
- 客户端用服务端RSA公钥,通过RSA-OAEP加密本次的AES密钥
- 客户端封装加密后的AES密钥、业务数据、timestamp、nonce为统一请求体,发送至服务端
- 服务端校验timestamp时间窗口(默认5分钟),过期直接拒绝;校验nonce是否已使用,已使用则判定为重放请求
- 服务端用RSA私钥解密,拿到本次请求的AES密钥
- 服务端用AES密钥+相同AAD解密业务数据,反序列化为对应DTO对象
- 业务处理完成后,服务端用本次请求的AES密钥加密响应数据,返回给客户端
- 客户端用本地保存的AES密钥解密响应数据,拿到业务结果
2.3 项目工程结构
代码见 gitee: Trial5-api-security-demo
plain
src/main/java/com/storm/security/
├── annotation/ // 自定义注解
│ ├── DecryptRequestBody.java // 请求解密注解
│ └── EncryptResponseBody.java // 响应加密注解
├── config/ // 配置类
│ ├── EncryptionConfig.java // 密钥、工具类Bean配置
│ └── WebMvcConfig.java // 参数解析器、响应增强注册
├── constant/ // 常量类
│ └── EncryptionConstant.java // 算法、超时时间等常量
├── controller/ // 业务接口
│ ├── KeyController.java // 公钥获取接口
│ └── UserController.java // 业务测试接口
├── dto/ // 数据传输对象
│ ├── EncryptedRequest.java // 统一加密请求体
│ ├── EncryptedResponse.java // 统一加密响应体
│ └── UserDTO.java // 业务DTO示例
├── encryption/ // 加解密核心工具
│ ├── RsaUtil.java // RSA-OAEP加解密工具
│ ├── AesUtil.java // AES-GCM加解密工具
│ └── KeyPairManager.java // RSA密钥对管理(支持轮换)
├── exception/ // 异常处理
│ ├── EncryptionException.java // 加解密自定义异常
│ └── GlobalExceptionHandler.java// 全局异常统一处理
├── resolver/ // 参数解析器
│ └── DecryptRequestBodyResolver.java // 自动解密参数解析器
├── advice/ // 响应增强
│ └── EncryptResponseBodyAdvice.java // 自动加密响应增强
└── service/ // 核心调度
└── EncryptionService.java // 加解密核心调度服务
3 核心技术选型说明
3.1 RSA算法:必须统一RSA-OAEP,拒绝PKCS1Padding
大量教程都会踩的核心坑:Java默认的RSA/ECB/PKCS1Padding与前端WebCrypto API默认的RSA-OAEP完全不兼容,且PKCS1Padding属于老旧填充方式,存在明文攻击风险,已被NIST不推荐使用。
✅ 生产级正确做法:前后端统一使用RSA/ECB/OAEPWithSHA-256AndMGF1Padding,完全兼容WebCrypto,安全性更高。
3.2 AES算法:必须选GCM模式,拒绝CBC/CTR
| AES模式 | 是否推荐 | 核心问题 |
|---|---|---|
| AES-CBC | ❌ 不推荐 | 仅提供加密,无完整性校验,存在Padding Oracle攻击风险 |
| AES-CTR | ❌ 不推荐 | 流加密模式,无完整性校验,无法防篡改 |
| AES-GCM | ✅ 生产推荐 | 同时实现「加密+完整性认证+防篡改」,CPU有指令集加速,性能优异,天然支持AAD附加认证数据 |
GCM的AAD特性是防篡改的核心:我们将timestamp和nonce作为AAD数据,不参与加密但参与认证,一旦这两个字段被篡改,解密会直接抛出AEADBadTagException,从根本上杜绝请求被篡改的风险。
4 完整代码实现
采用 SpringBoot3.x + Java17 完成
4.1 核心依赖(pom.xml)
plain
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis(防重放nonce存储) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jackson JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
4.2 常量类(统一管理魔法值)
plain
public class EncryptionConstant {
// RSA算法配置
public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
public static final int RSA_KEY_SIZE = 2048;
// AES算法配置
public static final String AES_ALGORITHM = "AES/GCM/NoPadding";
public static final int AES_KEY_SIZE = 256;
public static final int GCM_IV_LENGTH = 12;
public static final int GCM_TAG_LENGTH = 128;
// 防重放配置
public static final long REQUEST_EXPIRE_TIME = 5 * 60 * 1000L;
public static final long NONCE_EXPIRE_TIME = 5;
public static final TimeUnit NONCE_EXPIRE_UNIT = TimeUnit.MINUTES;
public static final String NONCE_REDIS_KEY_PREFIX = "security:nonce:";
// 字符集
public static final Charset CHARSET = StandardCharsets.UTF_8;
}
4.3 自定义异常类
plain
public class EncryptionException extends RuntimeException {
public EncryptionException(String message) {
super(message);
}
public EncryptionException(String message, Throwable cause) {
super(message, cause);
}
}
4.4 RSA-OAEP加解密工具类
plain
@Slf4j
public class RsaUtil {
public String encrypt(String data, Key publicKey) {
try {
Cipher cipher = Cipher.getInstance(EncryptionConstant.RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedData = cipher.doFinal(data.getBytes(EncryptionConstant.CHARSET));
return Base64.getEncoder().encodeToString(encryptedData);
} catch (Exception e) {
log.error("RSA加密失败", e);
throw new EncryptionException("RSA加密失败", e);
}
}
public String decrypt(String encryptedData, Key privateKey) {
try {
Cipher cipher = Cipher.getInstance(EncryptionConstant.RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
byte[] decryptedData = cipher.doFinal(decodedData);
return new String(decryptedData, EncryptionConstant.CHARSET);
} catch (Exception e) {
log.error("RSA解密失败", e);
throw new EncryptionException("RSA解密失败,密钥不匹配或数据被篡改", e);
}
}
}
4.5 AES-GCM加解密工具类
java
@Slf4j
public class AesUtil {
public String generateAesKey() {
byte[] key = new byte[EncryptionConstant.AES_KEY_SIZE / 8];
new SecureRandom().nextBytes(key);
return Base64.getEncoder().encodeToString(key);
}
public String encrypt(String data, String base64Key, String aad) {
try {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
if (keyBytes.length != EncryptionConstant.AES_KEY_SIZE / 8) {
throw new EncryptionException("AES密钥长度必须为256位");
}
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
byte[] iv = new byte[EncryptionConstant.GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance(EncryptionConstant.AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(EncryptionConstant.GCM_TAG_LENGTH, iv));
cipher.updateAAD(aad.getBytes(EncryptionConstant.CHARSET));
byte[] encryptedData = cipher.doFinal(data.getBytes(EncryptionConstant.CHARSET));
byte[] result = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encryptedData, 0, result, iv.length, encryptedData.length);
return Base64.getEncoder().encodeToString(result);
} catch (EncryptionException e) {
throw e;
} catch (Exception e) {
log.error("AES加密失败", e);
throw new EncryptionException("AES加密失败", e);
}
}
public String decrypt(String encryptedData, String base64Key, String aad) {
try {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
if (keyBytes.length != EncryptionConstant.AES_KEY_SIZE / 8) {
throw new EncryptionException("AES密钥长度必须为256位");
}
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
byte[] iv = new byte[EncryptionConstant.GCM_IV_LENGTH];
byte[] cipherBody = new byte[decodedData.length - EncryptionConstant.GCM_IV_LENGTH];
System.arraycopy(decodedData, 0, iv, 0, iv.length);
System.arraycopy(decodedData, iv.length, cipherBody, 0, cipherBody.length);
Cipher cipher = Cipher.getInstance(EncryptionConstant.AES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(EncryptionConstant.GCM_TAG_LENGTH, iv));
cipher.updateAAD(aad.getBytes(EncryptionConstant.CHARSET));
byte[] decryptedData = cipher.doFinal(cipherBody);
return new String(decryptedData, EncryptionConstant.CHARSET);
} catch (EncryptionException e) {
throw e;
} catch (Exception e) {
log.error("AES解密失败", e);
throw new EncryptionException("AES解密失败,密钥不匹配、数据被篡改或AAD不一致", e);
}
}
}
4.6 RSA密钥对管理(支持平滑轮换)
java
@Getter
public class KeyPairManager {
private final RSAPublicKey publicKey;
private final RSAPrivateKey privateKey;
private final String publicKeyBase64;
// 生产环境建议从配置中心/KMS加载,支持热更新与平滑轮换
public KeyPairManager() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(EncryptionConstant.RSA_KEY_SIZE);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
this.publicKey = (RSAPublicKey) keyPair.getPublic();
this.privateKey = (RSAPrivateKey) keyPair.getPrivate();
this.publicKeyBase64 = Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
}
4.7 配置类
java
@Configuration
public class EncryptionConfig {
@Bean
public KeyPairManager keyPairManager() throws Exception {
return new KeyPairManager();
}
@Bean
public RsaUtil rsaUtil() {
return new RsaUtil();
}
@Bean
public AesUtil aesUtil() {
return new AesUtil();
}
}
4.8 统一请求与响应DTO
java
// 加密请求体
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EncryptedRequest {
private String encryptedKey;
private String encryptedData;
private Long timestamp;
private String nonce;
}
// 加密响应体
package com.example.security.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EncryptedResponse {
private String encryptedData;
private int code;
private String msg;
}
// 业务DTO示例
package com.example.security.dto;
import lombok.Data;
@Data
public class UserDTO {
private String username;
private String password;
private String phone;
private String email;
}
4.9 自定义注解
java
// 请求解密注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequestBody {
}
// 响应加密注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptResponseBody {
}
4.10 核心加解密调度服务
java
@Slf4j
@Service
@RequiredArgsConstructor
public class EncryptionService {
private final KeyPairManager keyPairManager;
private final RsaUtil rsaUtil;
private final AesUtil aesUtil;
private final ObjectMapper objectMapper;
private final StringRedisTemplate redisTemplate;
private static final ThreadLocal<String> AES_KEY_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<String> AAD_HOLDER = new ThreadLocal<>();
// 请求解密
public <T> T decrypt(EncryptedRequest request, Class<T> targetClass) {
try {
// 时间窗口校验
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - request.getTimestamp()) > EncryptionConstant.REQUEST_EXPIRE_TIME) {
throw new EncryptionException("请求已过期,请重新发起");
}
// nonce幂等校验
String nonceKey = EncryptionConstant.NONCE_REDIS_KEY_PREFIX + request.getNonce();
Boolean isFirstRequest = redisTemplate.opsForValue()
.setIfAbsent(nonceKey, "1", EncryptionConstant.NONCE_EXPIRE_TIME, EncryptionConstant.NONCE_EXPIRE_UNIT);
if (Boolean.FALSE.equals(isFirstRequest)) {
throw new EncryptionException("重复请求,禁止访问");
}
// RSA解密获取AES密钥
String aesKey = rsaUtil.decrypt(request.getEncryptedKey(), keyPairManager.getPrivateKey());
// 构建AAD,与前端完全一致
String aad = request.getTimestamp() + ":" + request.getNonce();
// AES解密业务数据
String jsonData = aesUtil.decrypt(request.getEncryptedData(), aesKey, aad);
// 存入ThreadLocal用于响应加密
AES_KEY_HOLDER.set(aesKey);
AAD_HOLDER.set(aad);
// 反序列化为目标对象
return objectMapper.readValue(jsonData, targetClass);
} catch (EncryptionException e) {
throw e;
} catch (Exception e) {
log.error("请求解密失败", e);
throw new EncryptionException("请求解密失败", e);
}
}
// 响应加密
public String encrypt(Object data) {
try {
String aesKey = AES_KEY_HOLDER.get();
String aad = AAD_HOLDER.get();
if (aesKey == null || aad == null) {
throw new EncryptionException("响应加密失败,未获取到请求密钥信息");
}
String jsonData = objectMapper.writeValueAsString(data);
return aesUtil.encrypt(jsonData, aesKey, aad);
} catch (EncryptionException e) {
throw e;
} catch (Exception e) {
log.error("响应加密失败", e);
throw new EncryptionException("响应加密失败", e);
} finally {
// 清除ThreadLocal,避免内存泄漏
AES_KEY_HOLDER.remove();
AAD_HOLDER.remove();
}
}
}
4.11 自动解密参数解析器
java
@RequiredArgsConstructor
public class DecryptRequestBodyResolver implements HandlerMethodArgumentResolver {
private final EncryptionService encryptionService;
private final ObjectMapper objectMapper;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(DecryptRequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
EncryptedRequest encryptedRequest = objectMapper.readValue(request.getInputStream(), EncryptedRequest.class);
return encryptionService.decrypt(encryptedRequest, parameter.getParameterType());
}
}
4.12 自动加密响应增强
java
@RestControllerAdvice
@RequiredArgsConstructor
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final EncryptionService encryptionService;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(EncryptResponseBody.class)
|| returnType.getContainingClass().isAnnotationPresent(EncryptResponseBody.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
String encryptedData = encryptionService.encrypt(body);
return EncryptedResponse.builder()
.encryptedData(encryptedData)
.code(200)
.msg("success")
.build();
}
}
4.13 WebMvc配置与全局异常处理
java
// WebMvc配置
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final EncryptionService encryptionService;
private final ObjectMapper objectMapper;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new DecryptRequestBodyResolver(encryptionService, objectMapper));
}
}
// 全局异常处理
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EncryptionException.class)
public ResponseEntity<Map<String, Object>> handleEncryptionException(EncryptionException e) {
log.error("加解密异常:{}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("code", 400, "msg", e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
log.error("系统异常:{}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("code", 500, "msg", "系统内部错误"));
}
}
4.14 业务Controller示例
java
// 公钥获取接口
@RestController
@RequestMapping("/api/key")
@RequiredArgsConstructor
public class KeyController {
private final KeyPairManager keyPairManager;
@GetMapping("/public")
public ResponseEntity<Map<String, String>> getPublicKey() {
return ResponseEntity.ok(Map.of("publicKey", keyPairManager.getPublicKeyBase64()));
}
}
// 业务接口
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/register")
@EncryptResponseBody
public ResponseEntity<Map<String, Object>> register(@DecryptRequestBody UserDTO user) {
// 业务逻辑处理
System.out.println("收到用户注册请求:" + user);
return ResponseEntity.ok(Map.of(
"msg", "注册成功",
"username", user.getUsername(),
"userId", System.currentTimeMillis()
));
}
}
5 前端完整对接代码
1、获取公钥
javascript
const response = await fetch('/api/key/public');
const { publicKey } = await response.json();
2、加密请求
javascript
// 生成 AES 密钥
const aesKey = window.crypto.getRandomValues(new Uint8Array(32));
// 导入 RSA 公钥
const publicKeyObj = await window.crypto.subtle.importKey(
'spki',
base64ToArrayBuffer(publicKey),
{ name: 'RSA-OAEP', hash: 'SHA-256' },
false,
['encrypt']
);
// RSA 加密 AES 密钥
const encryptedKey = await window.crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
publicKeyObj,
aesKey
);
// 生成时间戳和 Nonce
const timestamp = Date.now();
const nonce = crypto.randomUUID();
const aad = `${timestamp}:${nonce}`;
// AES-GCM 加密业务数据
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const aesKeyObj = await window.crypto.subtle.importKey(
'raw',
aesKey,
{ name: 'AES-GCM' },
false,
['encrypt']
);
const encryptedData = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
additionalData: new TextEncoder().encode(aad),
tagLength: 128
},
aesKeyObj,
new TextEncoder().encode(JSON.stringify(userData))
);
// 发送请求
const response = await fetch('/api/user/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
encryptedKey: arrayBufferToBase64(encryptedKey),
encryptedData: arrayBufferToBase64(
concatenate(iv, new Uint8Array(encryptedData))
),
timestamp: timestamp,
nonce: nonce
})
});
6 生产级注意事项与踩坑指南
6.1 核心安全边界
- 本方案仅解决数据传输的加密、防篡改、防重放,不能替代身份认证体系,需与JWT、OAuth2等身份认证方案结合使用。
- HTTPS是基础,本方案是HTTPS之上的加固,绝对不能因为用了接口加密就关闭HTTPS。
- 不适合大文件传输、日志采集等高并发大流量场景,加解密会有性能开销。
6.2 密钥管理优化
- 生产环境禁止启动时生成密钥对,需将密钥对配置在配置中心(Nacos/Apollo),支持热更新与平滑轮换,建议3个月更换一次密钥。
- 高安全场景建议使用KMS密钥管理服务,不要将密钥硬编码在代码或配置文件中。
6.3 常见问题排查
- AES解密失败:AEADBadTagException
- 最常见原因:前后端AAD不一致,检查timestamp+nonce的拼接顺序、分隔符是否完全一致
- 其他原因:IV长度不是12字节、密钥不匹配、数据被篡改
- RSA解密失败:Data must not be longer than 245 bytes
- 原因:RSA仅用于加密AES密钥,禁止用RSA加密业务数据;2048位RSA最多加密245字节数据
- 其他原因:前后端填充方式不一致,必须统一RSA-OAEP
- 防重放误判
- 原因:客户端重试未更换nonce、前后端时间差超过5分钟、Redis配置错误导致nonce未正确写入
6.4 性能评估
| 操作 | 平均耗时 |
|---|---|
| RSA-OAEP 2048位解密 | ~0.8ms |
| AES-256-GCM加解密(1KB以内数据) | <0.1ms |
| 全链路加解密(含Redis校验) | ~1-2ms |
| 4核8G服务器单接口QPS | 3000+ |
常规业务系统(QPS 1000-5000)完全可以无压力使用,性能开销完全可控。
7 总结
如果你的系统只是普通的Web管理系统,HTTPS已经足够满足安全需求;但如果你面对的是高敏感数据传输、App/小程序开发、需要防逆向、防Hook、防网关日志泄露的场景,接口级加解密不是过度设计,而是必要的安全防线。本文提供的方案,从算法选型、工程化设计、前后端兼容、生产踩坑指南等多个维度,实现了一套可直接落地生产的全链路加解密方案,无需修改业务代码,只需添加注解即可实现接口的自动加解密。
更多容易可以看仓库文档:快速开始
我的仓库地址:gitee.com/liyongde/ja...
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。转载请携带链接,转载到微信公众号请勿选择原创,谢谢!
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!