RSA不能直接加密HTTP body,因其明文长度严格受限(如2048位密钥仅支持约245字节),而API JSON报文常达数百上千字节,直接加密会报“message too long”错误;真实方案是混合加密:客户端用服务端RSA公钥加密随机生成的AES密钥,再用该AES密钥加密JSON payload,打包Version、KeyEnc、PayloadEnc、Nonce、Tag等字段构成安全可扩展的结构体,并在服务端严格校验版本、密钥长度、Nonce/Tag长度及GCM认证通过后才解密。

为什么不能直接用RSA加密HTTP body
因为RSA有严格长度限制:1024位密钥最多加密117字节,2048位也只支持245字节左右。微服务API的JSON body动辄几百上千字节,直接调用 rsa.EncryptPKCS1v15 会报 crypto/rsa: message too long for RSA key size 错误。
真实场景里,你不是用RSA去“加密整个请求”,而是用它来安全传递一个临时对称密钥——这个思路叫“混合加密”。
- 客户端生成随机AES密钥(比如32字节)
- 用服务端公钥加密这个AES密钥,得到密文
aes_key_encrypted - 用该AES密钥加密原始JSON payload,得到
payload_encrypted - 把两个密文、IV、Nonce等一起打包成结构体发送
报文结构怎么定义才够安全又可扩展
别用裸JSON拼字段,定义明确的加密报文结构体。关键字段必须包含:
type EncryptedRequest struct {
Version string `json:"v"` // 协议版本,便于未来升级
KeyEnc []byte `json:"k"` // RSA加密后的AES密钥
PayloadEnc []byte `json:"p"` // AES-GCM加密后的payload
Nonce []byte `json:"n"` // AES-GCM需要的nonce(12字节)
Tag []byte `json:"t"` // GCM认证标签(16字节)
}
注意:Version 字段不能省——不同服务可能升级到AES-256-GCM或ChaCha20-Poly1305,靠它做路由和解密策略分发;Tag 必须显式传输,GCM模式下缺它就无法验证完整性。
立即学习“go语言免费学习笔记(深入)”;
不要把公钥或算法名塞进body里。这些属于通信契约,应由服务发现或配置中心统一管理,避免每次请求都重复传输冗余信息。
服务端解密时最容易忽略的校验点
解密不是“拿到密文→解→完事”,漏掉任意一环都可能被绕过:
- 先检查
Version是否在白名单内,未知版本直接拒收 - 用预加载的公钥反向验证
KeyEnc是否真由本服务私钥对应公钥加密(防止重放或错用其他服务公钥) - 解出AES密钥后,立刻检查长度是否为32字节(AES-256),否则panic
- 调用
aes.NewCipher前确认Nonce长度为12字节,Tag为16字节 - GCM解密必须用
cryptor.Open而非cryptor.Seal,且返回err为nil才代表认证通过
曾经有个线上bug:服务端没校验 Tag 长度,攻击者传入1字节伪造tag,Open 返回成功但解出乱码——结果下游解析JSON panic,引发雪崩。
密钥生命周期和轮换怎么落地
RSA密钥对不能永久用。生产环境必须支持多版本共存与平滑切换:
- 私钥文件命名带版本号,如
rsa_private_v1.pem、rsa_private_v2.pem - 服务启动时加载所有有效私钥到内存 map[string]*rsa.PrivateKey,key为版本号
- 解密时从请求
Version字段读取版本,查map获取对应私钥 - 轮换新密钥时,先上线新私钥,等旧密钥加密的请求自然过期(比如保留30天),再下线旧私钥
别让密钥轮换变成发布窗口期——微服务是持续部署的,密钥变更必须热加载。如果用KMS托管,确保Go client能动态拉取最新密钥版本,而不是启动时硬编码路径。


















