HTTPS 之外的响应加密:GCM、序列化与处理器顺序
技术背景: HTTPS 保护传输通道,但数据到达代理、网关或终端后仍然是明文。某些高敏业务会进一步要求响应体应用层加密,这与替代 HTTPS 是两回事。
应用层加密必须处理随机 IV、认证标签、序列化顺序、密钥分发和错误响应;如果先加密后修改响应,或重复使用 nonce,安全性会直接失效。
MetaLite 在外部响应处理器中统一完成序列化后的 GCM 加密,避免业务接口自行处理。本文先说明额外加密的适用场景,再结合处理器顺序验证边界。
一、应用层响应加密解决什么、不解决什么
网关接口统一返回 Resp,其中可以同时存在:
text
data 明文业务对象
encryptData 加密后的字符串
code/message 业务状态
启用响应加密后,处理链执行:
text
业务方法返回 Resp
→ 序列化 data
→ 加密写入 encryptData
→ 设置 HTTP 状态
→ 记录响应日志
→ 清除 data
→ 返回调用方
调用方仍能读取 code 和 message,再按约定解密 encryptData。
二、哪些响应会被加密
ApiRespEncryptHandler 只处理满足三项的结果:
- 请求参数属于
ExternalReq; - 结果属于
Resp且 data 非空; - 当前 appId 的调用方配置要求加密。
健康检查、第三方回调等没有使用 ExternalReq 的特殊接口不会进入这条逻辑。
错误响应如果 data 为 null,也不会产生 encryptData。协议设计需要确保错误 message 中不包含本应加密的敏感数据。
三、当前支持哪些算法
执行分支支持:
text
SM4-GCM
SM4-CBC
AES-GCM
AES-CBC
且当前只支持对称加密类型。
后台配置注释即使出现更广的算法名称,也不能证明响应处理器已经实现;运行时遇到其他类型或算法会抛出 ServiceException。
这也是配置后台必须与执行分支共享枚举和校验的原因。
四、为什么优先考虑 GCM
GCM 属于 AEAD 模式,同时提供:
- 机密性;
- 完整性校验;
- 篡改检测。
CBC 只解决机密性,还需要独立 MAC 并采用 Encrypt-then-MAC 才能可靠检测篡改。
如果 CBC 工具只返回 IV + 密文而没有认证标签,调用方无法安全区分"密文被修改"和"解密后恰好得到垃圾数据"。
因此新协议通常优先使用 AES-GCM 或 SM4-GCM,并保证每次加密使用唯一随机 nonce/IV。
五、为什么不能先清 data 再加密
加密处理器从 resp.data 获取明文:
java
String plaintext = FastJson.obj2Json(resp.getData());
如果清理处理器先执行,得到的只能是 null。
MetaLite 使用 @Order 让加密处理器早于明文清理处理器,处理器链按升序执行。最终清理器检查 encryptData 已生成后才设置:
java
resp.setData(null);
这体现了有序处理器链的价值:加密、状态、日志和清理不是彼此独立的几个切面,它们有数据依赖。
六、当前为什么会在日志中保留明文
处理顺序把响应日志放在明文清理之前,源码注释明确表示为了调试记录完整明文。
这意味着外部响应虽然不再包含 data,应用日志仍可能保存敏感业务对象。
如果应用层加密的威胁模型包含"日志平台或运维访问不应看到明文",当前顺序就破坏了目标。
更安全的日志策略是:
- 记录 code、耗时、TraceId 和数据摘要;
- 对允许字段脱敏;
- 敏感接口完全不记录 body;
- 需要问题定位时使用受控采样和临时开关;
- 不记录密钥、明文或可重放请求。
调试便利不能默认高于数据最小化原则。
七、应用层加密不等于签名
响应密文即使使用 GCM,也只证明持有同一密钥的一方生成了合法认证标签。
在多调用方共享密钥、密钥权限过宽或需要不可否认性的场景,还要单独设计身份认证与签名。
请求侧的摘要签名、时间戳和响应加密解决不同问题:
text
调用方认证:谁发起请求
防重放:请求是否过期或重复
响应加密:谁能读取 data
响应完整性:内容是否被改动
不能因为返回了 encryptData 就省略 HTTPS、调用方认证或重放防护。
八、密钥如何按调用方隔离
处理器按 appId 读取调用方缓存中的:
text
encryptType
encryptAlgorithm
encryptSecret
为不同调用方配置不同密钥,可以缩小单个密钥泄漏影响范围。
但当前密钥作为调用方实体字段在数据库、缓存和管理链路中流转。完整治理还需要:
- 密钥管理系统托管;
- 管理页面只显示掩码;
- 日志与审计不保存密钥值;
- 密钥版本;
- 双密钥轮换窗口;
- 使用权限与访问审计;
- 泄漏后的快速吊销。
直接替换密钥会让调用方无法解密切换前在途响应,因此版本必须进入协议或双方切换流程。
九、序列化协议也属于加密契约
密文中的明文不是任意 Java 对象,而是 FastJson2 生成的 JSON。
调用方解密后还要知道:
- 字符编码;
- 时间格式;
- long 的表示方式;
- null 字段策略;
- 字段命名;
- JSON Schema 版本。
加密不会解决序列化兼容问题,反而让抓包无法直接观察内容。因此接口版本和测试向量更重要。
建议提供固定的请求、IV/nonce、密钥版本和期望密文示例,双方可以自动验证实现一致性,但生产请求必须使用新 nonce,不能复用测试向量。
十、应用层响应加密的验收清单
text
只对明确调用方启用
优先使用 GCM 等 AEAD
每次 nonce/IV 唯一
密钥按调用方隔离并可轮换
加密成功后才清理 data
日志不保存敏感明文
错误响应不泄露业务数据
序列化格式有版本和测试向量
仍然使用 HTTPS
MetaLite 的处理器链已经把"序列化、加密、状态、日志、清理"的执行顺序显式化。
真正决定安全强度的,不只是算法名称,而是 nonce、密钥生命周期、日志策略和协议兼容是否一起闭环。
十一、处理器顺序需要一组不可交换测试
可以为同一个 Resp.data 构造四组回归:正常明文响应、启用 GCM、业务异常响应、序列化失败。测试不仅比较 encryptData 是否存在,还要确认加密前数据没有提前清空、失败响应是否按协议处理、日志中保留的是允许排查的内容而不是完整敏感明文。
再交换一次日志处理器与 ApiRespEncryptHandler 的顺序,观察日志内容变化。这个反例能直接说明 @Order 不是代码排版,而是响应安全协议的一部分。
若前端和网关使用不同序列化配置,即使密钥、IV 和算法都正确,解密后的字节也可能无法还原业务对象,因此测试向量必须固定序列化结果。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目 backend-bom 为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026