# gcm
**Repository Path**: zengyufei/gcm
## Basic Information
- **Project Name**: gcm
- **Description**: gcm 项目啊啊啊啊
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2026-02-05
- **Last Updated**: 2026-03-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# crypto-spring-boot-starter 项目功能总结
> 一款面向企业级微服务的 **开箱即用安全通信中间件**,以 Spring Boot Starter 形式提供零侵入的加解密能力,覆盖接口防篡改、重放攻击拦截、大文件流式加密、安全审计与监控等完整安全链路。
---
## 一、整体目标
解决服务间(或客户端与服务端间)在 HTTP 通信层面的安全痛点:
| 痛点 | 解决方案 |
|---|---|
| 数据明文在网络中传输,可被抓包 | RSA + AES-CTR 混合加密,端到端全程密文 |
| 攻击者重放旧请求(Replay Attack) | Timestamp 时间窗口 + Nonce 一次性校验 |
| 中间人篡改请求体 | HMAC-SHA256 / RSA-SHA256 签名校验 |
| 大文件全量加密导致 OOM | 分片 + AES-CTR 流式并发加解密,内存恒定 |
| 密钥泄露后无法快速切换 | 多版本 KID 密钥管理,支持无感轮转 |
| 加密行为对业务代码有侵入 | 过滤器 + 注解切面,Controller 层零感知 |
---
## 二、加密技术选型与原因
本项目的每一项加密技术选型都有相对应的备选方案被权衡和放弃,以下逐一说明。
### 2.1 为什么用 RSA + AES 混合加密,而不是单独用 RSA 或纯 AES?
| 方案 | 问题 |
|---|---|
| **纯 RSA 加密** | RSA 单次只能加密约 200 字节(受密钥长度限制),无法直接加密 HTTP Body,且速度极慢(约是 AES 的 1000 倍),高并发场景 CPU 会直接打满 |
| **纯 AES 对称加密** | 密钥如何安全地分发给对方?硬编码在配置里等同于裸奔;如果在握手阶段传输密钥,中间人可以截获 |
| **RSA + AES 混合(本项目选型)** | RSA 只用来加密一个随机生成的一次性 AES 密钥(仅几十字节),Body 用 AES 高速加密。既解决了密钥分发问题(非对称),又保证了加密性能(对称)。这是 TLS 的同款思路 |
**结论:** RSA 负责安全传递密钥,AES 负责高效加密数据,两者各司其职,是工业界成熟的最优解。
---
### 2.2 为什么普通请求用 AES-GCM,文件传输用 AES-CTR,而不用 AES-CBC?
本项目根据场景特性,为两种不同的数据传输场景各自选择了最合适的 AES 工作模式:
| 场景 | 选型 | 原因 |
|---|---|---|
| **普通 JSON 请求/响应** | AES-GCM | Body 数据量小,可一次性完整处理;GCM 自带 AEAD 认证 Tag,加密与完整性校验一步到位,安全强度更高 |
| **大文件分片传输** | AES-CTR | 文件需要分片后**并发加密**;CTR 模式支持随机访问(Random Access),每个分片只需知道自身 offset 即可独立计算专属 IV,各线程互不依赖 |
**AES-GCM 为什么不用于大文件?**
GCM 必须处理完所有数据后才能生成完整的认证 Tag(Authentication Tag),无法在不知道全部内容时对某一分片单独计算有效 Tag。若强行分片,每片的 Tag 都是无效的,完整性保障形同虚设。
**AES-CTR 为什么不用于普通请求?**
CTR 模式没有内建完整性校验,单独使用时需要靠外部机制(如 HMAC 签名)补足。对普通请求而言,GCM 的 AEAD 特性天然兼具加密+校验,无需额外处理,更简洁安全。
**AES-CBC 为什么两个场景都不用?**
CBC 需要对齐块边界(Padding),存在 Padding Oracle 攻击风险;不支持并行处理(每块依赖上一块的密文);也不支持随机访问。在 GCM 和 CTR 均优于它的情况下,CBC 没有被选择的理由。
---
### 2.3 为什么选 HMAC-SHA256 做签名,备选 RSA-SHA256,而不是 MD5 或 SHA1?
| 方案 | 问题 |
|---|---|
| **MD5 签名** | 已被证明存在碰撞攻击(Collision Attack),2004 年起不再推荐用于安全场景 |
| **SHA1 签名** | 2017 年 Google 证明了 SHA1 的实际碰撞(SHAttered 攻击),同样不安全 |
| **HMAC-SHA256(本项目默认)** | 基于 SHA-256(抗碰撞),并在哈希中混入 HMAC 密钥,即使攻击者知道明文和哈希值,没有密钥也无法伪造签名。性能优秀,适合高频接口 |
| **RSA-SHA256(本项目可选)** | 利用 RSA 私钥签名,任何拥有公钥的人都能验证,适合需要"不可抵赖性"(Non-Repudiation)的场景(证明是某个私钥持有方发出的);但签名计算比 HMAC 慢约 10 倍 |
**结论:** 默认选 HMAC-SHA256(高频接口性能优先),对需要法律级别不可抵赖的场景可切换为 RSA-SHA256,配置项 `crypto.signature.default-algorithm` 控制。
---
### 2.4 为什么用 Timestamp + Nonce 双重防重放,而不是单独用一种?
| 方案 | 漏洞 |
|---|---|
| **只用 Timestamp(时间窗口)** | 时间窗口内(如 5 分钟),同一个请求可以被无限次重放 |
| **只用 Nonce(随机唯一 ID)** | Nonce 缓存必须永久保留,存储无限增长;系统重启后缓存丢失,历史 Nonce 可被重用 |
| **Timestamp + Nonce 双重(本项目选型)** | Timestamp 限制了重放的时间范围(5 分钟窗口);Nonce 在这 5 分钟窗口内保证唯一性;两者结合,Nonce 缓存的 TTL 只需与时间窗口一致(5 分钟后自动过期),存储完全可控 |
---
### 2.5 为什么用 Guava RateLimiter(令牌桶),而不是计数器(固定窗口/滑动窗口)?
| 方案 | 问题 |
|---|---|
| **固定窗口计数器** | 存在"临界突刺"问题:窗口末尾和下一窗口开始时,实际流量可能达到限制值的 2 倍 |
| **滑动窗口计数器** | 解决了突刺问题,但实现复杂,内存占用较高(需要存储每个请求的时间戳) |
| **令牌桶(Token Bucket,本项目选型)** | 允许短时突发(桶内有积攒的令牌),但长期平均速率受控;实现简单(Guava 一行代码);是大多数 API 网关(如 Nginx、Kong)的选型 |
---
### 2.6 为什么大文件合并使用 NIO `FileChannel.transferTo`(零拷贝),而不是普通的 InputStream 复制?
| 方案 | 问题 |
|---|---|
| **普通 InputStream → OutputStream 复制** | 数据路径:磁盘 → 内核缓冲区 → 用户空间 JVM 堆 → 内核缓冲区 → 磁盘,中间经过两次内核/用户空间切换,50GB 文件会产生巨大的内存压力和 CPU 复制开销 |
| **NIO FileChannel.transferTo(本项目选型)** | 数据路径:磁盘 → 内核缓冲区 → 磁盘,由操作系统内核直接完成(DMA 传输),JVM 堆内存占用接近于零,速度接近硬件上限(和 `cp` 命令一个量级) |
---
## 三、项目模块划分
```
crypto-spring-boot-starter/
├── crypto-spring-boot-starter-core # 加密算法引擎(全局基础)
├── crypto-spring-boot-starter-server # 服务端解密 / 签名校验过滤器
├── crypto-spring-boot-starter-client # 客户端加密发送 SDK
├── crypto-spring-boot-starter-oss # 大文件分片流式加密上传/下载
├── crypto-spring-boot-starter-redis # 分布式 Nonce 缓存(防重放)
├── crypto-spring-boot-starter-rate-limiter # 限流与 IP 黑名单
├── crypto-spring-boot-starter-audit # 安全审计日志
├── crypto-spring-boot-starter-monitoring # Micrometer 指标采集
└── crypto-spring-boot-starter-samples/
├── example-server # 服务端接入示例
└── example-client # 客户端接入示例
```
---
## 三、各模块功能详解
### 3.1 Core 模块 — 算法引擎
**核心加密模型:** 每次通信生成一次性 AES 密钥,用 RSA 公钥加密后随密文一起发出。
```
明文 Body
→ 随机 AES-CTR 密钥 + IV 加密 → 密文 data
→ RSA 公钥加密 AES 密钥 → encKey
→ 拼装 EncryptedRequest {data, encKey, iv, timestamp, nonce, kid, signature}
→ 发送
```
**关键类:**
- `EncryptedRequest / EncryptedResponse` — 统一的加密通信载荷结构,包含 `kid`(密钥版本)、`nonce`(防重放)、`timestamp`(时间窗口)、`signature`(防篡改)
- `AesGcmUtil` — AES-GCM 加解密,配合 `CipherPool` 对象池避免高并发下的 Cipher 创建开销
- `RsaKeyManager` — RSA 密钥管理门面,支持按 `kid` 版本号取对应密钥,支持文件和配置两种来源
- `SignatureUtil` — HMAC-SHA256 / RSA-SHA256 双选签名/校验工具
### 3.2 Server 模块 — 服务端防线
**核心流程:** 通过 Servlet 过滤器对所有请求进行前置处理,业务 Controller 永远只看到解密后的明文。
```
加密请求到达
→ EncryptionFilter
├─ 1. 时间窗口校验(防止过期报文)
├─ 2. Nonce 去重校验(防重放,调用 NonceCache)
├─ 3. 签名验证(防篡改,HMAC 或 RSA)
├─ 4. RSA 解密 AES 密钥
└─ 5. AES 解密 Body → 明文塞入 DecryptHttpServletRequestWrapper
→ 业务 Controller(直接 @RequestBody 接收明文对象)
→ 响应体被 EncryptHttpServletResponseWrapper 截获 → AES 加密 → 加密响应返回
```
**关键切面注解:**
- `@AutoDecrypt` — 标注在 Controller 方法上,声明该接口需要解密请求 / 可选加密响应
- `@AutoFileDecrypt` — 文件上传场景专用,解密 Multipart 文件流
**防重放:** `ReplayAttackValidator` 强校验 `timestamp` 落在配置的时间窗口内,且 `nonce` 不在缓存记录中(已见过则拒绝)。
### 3.3 Client 模块 — 客户端透明代理
**目的:** 服务间调用时,调用方一行代码发起加密请求,不感知任何底层加密细节。
```java
// 调用方只需这一行,底层自动完成:生成 AES 密钥→加密 Body→RSA 封装→带 Nonce/Timestamp/签名发出
String response = cryptoClientService.sendEncryptedRequest(url, requestBody);
```
**自动装配:** `CryptoClientAutoConfiguration` 根据 `crypto.client.enabled=true` 自动注册 `CryptoClientService` 和 `RestTemplate`。
### 3.4 OSS 模块 — 大文件流式加密
**核心设计:** 根据文件大小自动选择两条路径,全程不将整个文件加载进内存。
#### 小文件路径(< 50MB)
```
本地文件 → AES-CTR 流式加密 → 临时密文文件 → POST /api/file/oss/upload
→ 服务端 @AutoFileDecrypt 拦截 → 流式解密 → 业务 Controller 收到明文 MultipartFile
```
#### 大文件路径(>= 50MB)
```
1. 计算文件 MD5 → POST /chunk/check(秒传检测 / 断点续传查询)
2. 生成整体 AES-CTR 密钥与 Base IV
3. POST /chunk/init → 服务端创建 .metadata.json(记录进度)
4. 多线程并发分片加密上传:
每个线程按 offset 独立计算专属 IV(AES-CTR 的随机访问特性)→ encrypt → POST /chunk/upload
服务端:JVM 分段锁 + OS 文件排他锁 → 写入 chunk_xxxx.dat + 更新 metadata.json
5. POST /chunk/merge → 服务端 NIO FileChannel.transferTo 零拷贝合并所有分片
6. POST /chunk/finalize(传入回调 URL)→ 服务端从磁盘读明文文件,内网 HTTP 转发到业务接口
业务 Controller 透明收到完整明文大文件
```
**并发安全保障(双重锁):**
| 层级 | 机制 | 作用 |
|---|---|---|
| JVM 内 | `ReentrantLock` 分段锁(256 段) | 防同一进程内多线程争抢同一 metadata.json |
| 跨进程 | `FileChannel.lock()` OS 文件排他锁 | 防集群多实例并发写入同一文件 |
**简化版 API(新增):**
```java
// 一行搞定,内部自动判断小/大文件,路径和 kid 由配置驱动
String response = ossClient.upload(SERVER_BASE_URL, "file", filePath, extraParams);
```
对应配置:
```yaml
crypto:
oss:
default-kid: v1
upload-path: /api/file/oss/upload # 小文件加密上传(带 @AutoFileDecrypt)
plain-upload-path: /api/file/oss/upload/plain # 大文件 finalize 明文回调
```
### 3.5 Redis 模块 — 分布式 Nonce 缓存
**目的:** 在集群部署场景下,防重放 Nonce 需要跨实例共享。
- `RedisNonceCache` — 使用 Redis `SETNX`(原子性)存储 Nonce,TTL 与时间窗口一致
- `GuavaNonceCache` — 本地内存实现,单机降级使用
- **降级机制:** 当 Redis 不可用时,`RedisNonceCache` 自动降级为 `GuavaNonceCache`,保证服务不中断
### 3.6 Rate-Limiter 模块 — 限流与 IP 黑名单
**执行顺序:** 优先级最高,在所有加密过滤器之前执行,最大程度节省计算资源。
- `RateLimiterFilter` — 基于 Guava `RateLimiter`(令牌桶算法)限制每分钟请求数
- `IpBlacklistManager` — 当某 IP 的错误次数(如签名失败、重放尝试)超过阈值,自动加入黑名单,封禁后直接返回 403
### 3.7 Audit 模块 — 安全审计日志
记录每个加密相关事件,供安全团队事后溯源:
| 事件类型 | 触发场景 |
|---|---|
| `ENCRYPT` | 响应被加密 |
| `DECRYPT` | 请求被解密 |
| `REPLAY_ATTACK` | Nonce 重复或 Timestamp 超窗 |
| `SIGNATURE_FAIL` | 签名验证失败 |
| `API_ACCESS` | 普通接口访问 |
**字段:** 事件 ID、类型、用户、来源 IP、使用的 kid、耗时、结果(SUCCESS/FAILURE)、错误信息
**存储:** `AuditLogStorage` 接口,默认实现为 `FileAuditLogStorage`(写本地文件),可扩展接入 ELK/数据库。
### 3.8 Monitoring 模块 — 指标采集
通过 `CryptoMetricsCollector` 接入 Spring Boot Actuator + Micrometer,暴露以下计数指标:
| 指标名 | 含义 |
|---|---|
| `crypto.encrypt.count` | 加密次数 |
| `crypto.decrypt.count` | 解密次数 |
| `crypto.replay.attack.count` | 重放攻击拦截次数 |
| `crypto.cache.hits` / `misses` | Nonce 缓存命中/未命中 |
| `crypto.signature.fail.count` | 签名验证失败次数 |
指标可对接 Prometheus + Grafana 构建安全监控大盘。
---
## 四、关键设计原则
### 零侵入(对业务代码无污染)
- 服务端开发者只需在方法上加 `@AutoDecrypt` 注解,Controller 内部代码与无加密时完全相同
- 客户端开发者只需一行 `sendEncryptedRequest` 或 `ossClient.upload`,底层细节全部封装
### 约定大于配置
- 所有功能默认提供合理默认值(默认 kid=v1、默认上传路径、默认 Guava 缓存等)
- 通过 `@ConditionalOnProperty` 按需激活,不引入的模块不产生任何 Bean
### 高可用(容灾降级)
- Redis 宕机 → Guava 本地缓存自动接管
- 过期分片清理、断点续传恢复、临时文件自动清理均有保障
### 高性能
- AES-CTR 分片可并发加解密(无串行依赖)
- `CipherPool` 对象池复用避免 Cipher 频繁创建
- NIO `FileChannel.transferTo` 零拷贝合并大文件
---
## 五、接入只需三步
### 服务端
```xml
com.example
crypto-spring-boot-starter-server
```
```yaml
# application.yml 配置密钥和开关
crypto:
server:
enabled: true
rsa:
key-source: config
pkcs:
keys:
- kid: v1
publicKey: "..."
privateKey: "..."
signature:
default-algorithm: HMAC-SHA256
hmac:
secret: "your-secret"
```
```java
// Controller 方法加一个注解,搞定
@PostMapping("/api/user")
@AutoDecrypt(encryptResponse = true)
public ApiResponse createUser(@RequestBody User user) {
// 收到的 user 已是明文,正常使用
return ApiResponse.success(userService.save(user));
}
```
### 客户端
```java
// 注入 CryptoClientService,发起加密请求
@Autowired
private CryptoClientService cryptoClientService;
String response = cryptoClientService.sendEncryptedRequest("http://server/api/user", user);
```
### 文件上传(OSS)
```java
// 注入 OssClient,一行完成小文件/大文件智能上传
@Autowired
private OssClient ossClient;
String response = ossClient.upload("http://server", "file", localFilePath, null);
```
---
## 六、配置速查表
```yaml
crypto:
server:
enabled: true # 开启服务端解密过滤器
replay-attack:
enabled: true
time-window-minutes: 5 # 防重放时间窗口(分钟)
signature:
default-algorithm: HMAC-SHA256 # HMAC-SHA256 或 RSA-SHA256
hmac:
secret: "xxx"
rsa:
key-source: config # config 或 file
cache:
enabled: true
type: guava # guava 或 redis 或 auto
rate-limiter:
enabled: true
max-requests-per-minute: 300
max-violations: 50
audit:
enabled: true
file:
path: ./logs/crypto-audit.log
monitoring:
enabled: true
oss:
default-kid: v1
upload-path: /api/file/oss/upload
plain-upload-path: /api/file/oss/upload/plain
file-encryption:
mode: chunk # chunk 或 concurrent
```