# 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 ```