Web接口业务数据加密传输方案:算法选型与落地实践
前言
在 Web 项目中,接口安全最基础的一层一定是 HTTPS。HTTPS 已经通过 TLS 解决了传输过程中的窃听、篡改和中间人攻击问题。对于大多数普通业务接口来说,HTTPS 加登录态认证已经足够。
但在一些敏感场景中,比如实名认证、银行卡绑定、支付确认、修改手机号、修改密码、隐私资料提交等,仅依赖 HTTPS 有时还不够。原因不是 HTTPS 不安全,而是业务系统里还可能存在日志落盘、代理转发、网关调试、内部链路暴露、前后端错误处理等额外风险。
这时可以在 HTTPS 之外,再对敏感业务数据做一层应用层加密。本文就从算法对比开始,介绍一套比较通用的接口业务数据加密传输方案。
常见加密算法对比
在设计接口加密方案之前,先要理解几类常见算法的特点。很多安全方案不是靠某一个算法完成的,而是多种算法各自负责一部分能力。
对称加密算法
对称加密的特点是:加密和解密使用同一把密钥。
常见算法有 AES、ChaCha20、SM4、DES、3DES 等。
| 算法 | 特点 | 缺点 | 建议 |
|---|---|---|---|
| AES | 标准成熟、性能好、应用最广 | 需要安全传递密钥;模式选错会有风险 | 推荐 |
| AES-GCM | 加密同时提供完整性校验,适合接口数据加密 | IV 不能在同一密钥下重复使用 | 强烈推荐 |
| ChaCha20-Poly1305 | 移动端性能好,对硬件 AES 加速依赖低 | 国内传统后端栈支持度可能不如 AES | 推荐 |
| SM4 | 国产商用密码算法,适合国密合规场景 | 非国密场景生态不如 AES 普遍 | 合规场景推荐 |
| DES | 历史算法,密钥太短 | 已不安全 | 不推荐 |
| 3DES | 比 DES 稍强,但慢且老旧 | 已逐步淘汰 | 不推荐 |
业务数据加密通常选择:
1 | AES-256-GCM |
AES 负责加密正文,GCM 模式还能校验密文是否被篡改,比传统的 AES-CBC 更适合新项目。
非对称加密算法
非对称加密使用一对密钥:
1 | 公钥:可以公开,用于加密或验签 |
常见算法有 RSA、ECC、SM2 等。
| 算法 | 特点 | 缺点 | 建议 |
|---|---|---|---|
| RSA-OAEP | 成熟、兼容性好,适合加密临时 AES 密钥 | 速度慢,不适合加密大数据 | 推荐 |
| RSA PKCS#1 v1.5 | 老系统常见 | 安全性不如 OAEP,不建议新项目使用 | 谨慎使用 |
| ECC / ECDH | 密钥短、性能好,适合密钥协商 | 实现和兼容性比 RSA 更复杂 | 推荐给有经验团队 |
| SM2 | 国密非对称算法 | 非国密生态兼容性有限 | 合规场景推荐 |
非对称加密不适合直接加密整个请求体,因为它慢,而且可加密的数据长度有限。更常见的方式是混合加密:
1 | AES 加密业务数据 |
这样既有 AES 的性能,也有 RSA 解决密钥传递问题的能力。
哈希摘要算法
哈希不是加密,因为哈希结果不能解密回原文。它的作用是生成数据指纹。
常见算法有 SHA-256、SHA-512、SM3、MD5、SHA1。
| 算法 | 特点 | 缺点 | 建议 |
|---|---|---|---|
| SHA-256 | 通用、安全、支持广泛 | 只能做摘要,不能直接防伪造 | 推荐 |
| SHA-512 | 摘要更长,适合部分服务端场景 | 输出更长,未必必要 | 推荐 |
| SM3 | 国产摘要算法 | 非国密场景使用较少 | 合规场景推荐 |
| MD5 | 速度快,历史项目常见 | 已不安全,容易碰撞 | 不推荐 |
| SHA1 | 历史算法 | 已不安全 | 不推荐 |
注意:用户密码不要直接用 SHA-256、MD5 这类普通哈希保存。密码存储应使用 Argon2id、bcrypt 或 PBKDF2。
消息认证与签名算法
签名类算法主要用于防篡改和验身份。
常见方案有 HMAC-SHA256、RSA-SHA256、ECDSA、SM2 签名。
| 算法 | 特点 | 缺点 | 适合场景 |
|---|---|---|---|
| HMAC-SHA256 | 简单、快、实现稳定 | 双方必须共享同一个密钥 | 内部接口、业务接口 |
| RSA-SHA256 | 私钥签名、公钥验签,适合开放平台 | 性能比 HMAC 低 | 第三方接口、支付回调 |
| ECDSA | 签名短、性能好 | 实现细节要求高 | 现代证书与开放接口 |
| SM2 签名 | 国密签名算法 | 非国密生态支持有限 | 国密合规项目 |
如果接口数据已经使用 AES-GCM 加密,GCM 本身就带认证能力,可以发现密文是否被篡改。若系统还需要统一签名规范,可以额外增加 HMAC-SHA256。
推荐方案
对于普通 Web 项目的敏感接口,推荐方案如下:
1 | HTTPS |
这套方案中,每个部分的职责很清晰:
| 组件 | 作用 |
|---|---|
| HTTPS | 保护传输通道 |
| Token | 识别当前用户身份 |
| AES-256-GCM | 加密请求体中的敏感业务数据 |
| RSA-OAEP | 安全传递本次请求使用的 AES 密钥 |
| timestamp | 限制请求有效时间 |
| nonce | 防止同一请求被重复提交 |
| HMAC-SHA256 | 可选,用于统一接口签名和防篡改 |
整体思路是:不要把所有事情都交给一个算法。AES 负责快,RSA 负责安全传密钥,timestamp 和 nonce 负责防重放,签名负责防篡改。
下面这张图可以帮助理解各个组件之间的分工:
flowchart LR
A[浏览器客户端] -->|HTTPS 请求| B[服务端网关]
B --> C[业务服务]
C --> D[(Redis nonce 缓存)]
C --> E[(业务数据库)]
subgraph Client[客户端侧]
A1[生成 AES key]
A2[AES-GCM 加密业务数据]
A3[RSA-OAEP 加密 AES key]
A4[生成 timestamp 和 nonce]
end
subgraph Server[服务端侧]
S1[校验 Token]
S2[校验 timestamp]
S3[校验 nonce]
S4[RSA 私钥解密 AES key]
S5[AES-GCM 解密业务数据]
end
Client --> A
B --> Server
请求结构设计
原始业务数据可能是这样:
1 | { |
加密后,接口真正提交的数据可以设计成:
1 | { |
其中:
encryptedKey:前端生成 AES 密钥后,用服务端 RSA 公钥加密得到。iv:AES-GCM 加密使用的 IV,推荐 12 字节随机数。data:业务 JSON 序列化后加密得到的密文。timestamp:当前请求时间戳,用来限制请求有效期。nonce:随机字符串,用来防止重复请求。sign:可选字段,如果系统已有签名规范,可以把关键字段一起签名。
请求体的封装关系可以表示为:
flowchart TD
P[原始业务 JSON] --> A[AES-GCM 加密]
K[随机 AES key] --> A
IV[随机 IV] --> A
A --> DATA[加密后的 data]
K --> R[RSA-OAEP 公钥加密]
PUB[服务端 RSA 公钥] --> R
R --> EK[encryptedKey]
DATA --> REQ[接口请求体]
EK --> REQ
IV --> REQ
T[timestamp] --> REQ
N[nonce] --> REQ
SIGN[可选 sign] --> REQ
前端加密流程
前端处理流程如下:
1 | 1. 准备原始业务 JSON |
完整交互时序如下:
sequenceDiagram
participant C as 前端
participant S as 服务端
participant R as Redis
C->>C: 生成 AES key 和 IV
C->>C: AES-GCM 加密业务 JSON
C->>C: RSA-OAEP 加密 AES key
C->>C: 生成 timestamp 和 nonce
C->>S: 提交 encryptedKey、iv、data、timestamp、nonce、sign
S->>S: 校验 Token 和 timestamp
S->>R: 查询 nonce 是否已使用
R-->>S: 返回查询结果
S->>R: 写入 nonce,设置短 TTL
S->>S: RSA 私钥解密 AES key
S->>S: 校验签名并 AES-GCM 解密 data
S-->>C: 返回业务处理结果
伪代码如下:
1 | const payload = { |
实际开发时,浏览器端应使用 window.crypto.subtle 或成熟加密库,不要自己实现底层算法。
服务端解密流程
服务端收到请求后,不要马上解密业务数据,建议先做基础校验:
1 | 1. 校验登录态 Token |
服务端校验顺序建议保持“先低成本拒绝,再做高成本解密”:
flowchart TD
A[收到敏感接口请求] --> B{Token 是否有效}
B -- 否 --> X1[拒绝请求]
B -- 是 --> C{timestamp 是否过期}
C -- 是 --> X2[拒绝请求]
C -- 否 --> D{nonce 是否已使用}
D -- 是 --> X3[拒绝请求]
D -- 否 --> E[记录 nonce 并设置 TTL]
E --> F[RSA 私钥解密 AES key]
F --> G{签名是否正确}
G -- 否 --> X4[拒绝请求]
G -- 是 --> H[AES-GCM 解密 data]
H --> I[执行业务逻辑]
伪代码如下:
1 | async function handleSensitiveRequest(req) { |
这里有一个细节:比较签名时最好使用恒定时间比较函数,例如 Node.js 里的 crypto.timingSafeEqual,避免极端情况下的时序攻击。
IV 和 nonce 的区别
IV 和 nonce 都是随机值,但它们不是同一个东西。
| 字段 | 用途 | 谁使用 | 是否需要存储 |
|---|---|---|---|
| IV | 给 AES-GCM 加密和解密使用 | 加密算法 | 不需要长期存储,但请求中要传给服务端 |
| nonce | 防止请求被重复提交 | 业务服务 | 需要短期存入 Redis 等缓存 |
简单理解:
1 | IV:加密算法用的随机数 |
不要为了省字段把二者混用。它们的职责、生命周期和校验逻辑都不同。
nonce 由谁生成
nonce 可以由前端生成,也可以由后端生成。
前端生成 nonce
这是大多数普通敏感接口的做法:
1 | 前端生成 nonce |
优点是简单,不需要多一次请求。
适合:
- 修改个人资料
- 保存敏感配置
- 提交实名认证资料
- 普通隐私数据提交
前端生成 nonce 的防重放逻辑如下:
flowchart TD
A[前端生成 nonce] --> B[提交敏感请求]
B --> C[服务端读取 userId 和 nonce]
C --> D{Redis 中是否存在}
D -- 存在 --> E[判定为重复请求并拒绝]
D -- 不存在 --> F[写入 Redis,TTL 约 5 分钟]
F --> G[继续解密和处理业务]
后端生成 nonce
高风险操作可以改成后端下发一次性 nonce 或 challenge:
1 | 1. 前端请求后端获取 nonce |
优点是安全性更强。后端不仅能判断 nonce 有没有重复,还能判断它是不是由自己签发、是否属于当前用户、是否只能用于当前接口。
适合:
- 支付
- 提现
- 转账
- 修改密码
- 换绑手机号
- 绑定银行卡
- 签署合同
后端生成 nonce 更像一次性业务凭证:
sequenceDiagram
participant C as 前端
participant S as 服务端
participant R as Redis
C->>S: 请求操作凭证
S->>R: 保存 nonce、userId、action、expireAt
S-->>C: 返回 nonce 或 challenge
C->>S: 提交敏感操作和 nonce
S->>R: 校验 nonce 是否存在且未使用
R-->>S: 返回绑定信息
S->>S: 校验用户、接口、业务上下文
S->>R: 消费 nonce
S-->>C: 返回处理结果
密钥管理
这类方案里,密钥管理比算法本身更容易出问题。
前端可以持有:
1 | 服务端 RSA 公钥 |
服务端必须保护:
1 | RSA 私钥 |
注意事项:
- RSA 私钥不能放在前端。
- AES 密钥应每次请求临时生成,不要长期复用。
- AES-GCM 的 IV 在同一个 AES 密钥下不能重复。
- 服务端私钥应定期轮换,并支持多版本 keyId。
- 不要把解密后的敏感明文写入普通日志。
- 错误信息不要返回过多细节,例如不要告诉攻击者是签名错、密钥错还是 padding 错。
如果系统要支持密钥轮换,可以在请求里增加:
1 | { |
服务端根据 keyId 选择对应私钥解密。
密钥边界可以用下面的图来理解:
flowchart LR
subgraph Browser[前端浏览器]
PUB[服务端 RSA 公钥]
TEMP[本次请求临时 AES key]
IV2[本次请求 IV]
end
subgraph Backend[服务端]
PRI[服务端 RSA 私钥]
NONCE[(nonce 缓存)]
TOKEN[Token 验证密钥或会话]
end
subgraph Database[持久化系统]
DB[(业务数据库)]
LOG[(日志系统)]
end
PUB -->|可公开分发| Browser
TEMP -->|仅随请求临时存在| Browser
PRI -->|绝不下发前端| Backend
Backend --> DB
Backend --> LOG
什么时候不需要业务层加密
并不是所有接口都应该做业务层加密。
如果接口只是普通查询、非敏感配置、公开列表、普通文章内容,那么:
1 | HTTPS + 登录认证 |
就已经足够。
业务层加密会带来额外成本:
- 前后端复杂度增加
- 排查问题更困难
- 网关和日志无法直接观察业务字段
- 加密失败会带来新的错误场景
- 密钥轮换和兼容性需要额外设计
所以它更适合“少数敏感接口”,而不是全站所有接口。
常见错误做法
下面这些做法应尽量避免:
1 | 使用 AES-ECB |
安全方案最怕“看起来加密了”。真正可靠的方案,不只是密文看起来复杂,而是密钥、随机数、签名、防重放、日志、错误处理都要闭环。
总结
Web 敏感接口的业务层加密可以采用一套清晰的分工方案:
1 | HTTPS:保护传输通道 |
一句话概括:
1 | AES 负责加密数据,RSA 负责安全传递 AES 密钥,timestamp + nonce 负责防重放,HMAC 负责补充签名校验。 |
最终方案可以收敛成下面这条链路:
flowchart LR
A[业务 JSON] --> B[AES-256-GCM]
B --> C[密文 data]
D[AES key] --> E[RSA-OAEP]
E --> F[encryptedKey]
C --> G[HTTPS 请求]
F --> G
H[timestamp + nonce] --> G
I[HMAC-SHA256 可选] --> G
G --> J[服务端校验]
J --> K[解密并执行业务]
对于普通接口,不要过度设计,HTTPS 就是第一道也是最重要的安全边界。对于实名认证、支付、提现、换绑手机号等高风险接口,再使用业务层加密,才能在安全性和系统复杂度之间取得比较好的平衡。


