HTTPS接口国密安全设计(含防重放设计)

HTTPS接口国密安全设计

一、需求

1、总体安全需求

接口需要使用国密进行安全传输、防重放、防篡改和防伪造,敏感字段加密。

敏感字段是idCard和amount。

2、场景设定

客户端调用服务端接口

  • 客户端(App 或前端)向服务端(API 接口)发送请求。
  • 请求包含敏感参数(如用户身份证号、金额等),需加密。
  • 接口需防重放、防篡改、防伪造。

3、原始接口参数

json 复制代码
{
  "userId": "1001",
  "idCard": "110101199001011234",
  "amount": "5000.00",
  "timestamp": "1731123456789",
  "nonce": "abc123xyz"
}

二、设计与实现步骤

简单来说客户端实现

  • 将需要加密的字段通过sm4加密,然后进行base64编码

  • 将报文通过sm3进行报文摘要计算

  • 将报文摘要进行签名,然后进行base64编码

  • 将base64编码后的字符串(可选的公钥)作为参数

  • 完成请求参数构造

实现简单代码示例如下。

java 复制代码
// 原始报文
{
  "userId": "1001",
  "idCard": "110101199001011234",
  "amount": "5000.00",
  "timestamp": "1731123456789",
  "nonce": "abc123xyz"
}
// 需要加密的字段
"encryptedData": {
  "idCard": "110101199001011234",
  "amount": "5000.00"
}

// 1、将需要加密的字段通过sm4加密,然后进行base64编码
// cipherBytes 是二进制数据,不能直接放在 JSON 中传输
byte[] cipherBytes = SM4_CBC_Encrypt(encryptedData, key, iv);
// 经过Base64编码后的密文
String cipherText = Base64.encode(cipherBytes);

// 2、将报文通过sm3进行报文摘要计算
// 待签名的字符串,一般需要按照需要设定
待签名字符串="userId=1001&timestamp=1731123456789&nonce=abc123xyz&cipherText=Base64编码的SM4密文..."
digest = SM3(待签名字符串)

 // 3、将报文摘要进行签名,然后进行base64编码
 byte[] derSignature = SM2_Sign(client_private_key, digest); // 已经是 DER 编码
 String signature = Base64.encode(derSignature);        // 用于 JSON 传输
 
 // 4、最终构造的请求体
 {
  "userId": "1001",
  "cipherText": "Base64编码的SM4密文",// 来自于第一步
  "timestamp": 1731123456789,
  "nonce": "abc123xyz",
  "signature": "Base64编码的SM2签名", // 来自于第三步
  "publicKey": "客户端SM2公钥(首次调用时提供,可缓存)" // 一般是约定共享方式或作为参数传递
}

服务端实现

  • 将报文通过sm3进行摘要计算

  • 将公钥、报文摘要和客户端的签名进行sm2验证签名

  • 签名验证通过进行加密字段sm4解密

  • 正常业务处理

实现简单代码示例如下。

java 复制代码
// 原始报文
 {
  "userId": "1001",
  "cipherText": "Base64编码的SM4密文",// 来自于第一步
  "timestamp": 1731123456789,
  "nonce": "abc123xyz",
  "signature": "Base64编码的SM2签名", // 来自于第三步
  "publicKey": "客户端SM2公钥(首次调用时提供,可缓存)" // 一般是约定共享方式或作为参数传递
}

// 0、在进行报文sm3摘要计算之前可能会做些基础性的校验,比如防重放等

// 1、将报文通过sm3进行摘要计算
待签名字符串="userId=1001&timestamp=1731123456789&nonce=abc123xyz&cipherText=Base64编码的SM4密文..."
digest_server = SM3(待签名字符串) 
    
// 2、将公钥、报文摘要和客户端的签名进行sm2验证签名
isValid = SM2_Verify(publicKey, digest_server, signature)    
    
// 3、验证通过则进行sm4解密
// 通过共享的sm4密钥进行解密并结构化得到    
"encryptedData": {
  "idCard": "110101199001011234",
  "amount": "5000.00"
}

// 4、正常的业务逻辑处理
...

1、总体目标

  • 传输安全:使用国密 HTTPS(即基于 SM2/SM3/SM4 的 TLS 协议)
  • 接口参数加密:在应用层对敏感参数进行国密加密(使用 SM4)
  • 身份认证与防篡改:使用 SM2 数字签名 + SM3 摘要验证请求合法性
  • 符合国家密码管理局要求:满足等保、密评等合规需求

2、实现步骤

1)、建立国密 HTTPS 通道(传输层安全)
  • 使用算法

    SM2:用于服务器证书和密钥交换

    SM3:用于消息摘要和证书指纹

    SM4:用于加密会话数据(替代 AES)

  • 实现方式

    服务端申请国密数字证书(含 SM2 公钥)

    客户端使用支持国密的浏览器或 SDK(如 CFCA 国密浏览器、GmSSL 库)

  • 握手过程

    服务器发送 SM2 证书

    双方通过 SM2 密钥协商(ECDH)生成会话密钥

    使用 SM4 加密后续通信数据

    使用 SM3 计算握手消息完整性

结果:传输通道已加密,防止中间人窃听。

2)、客户端准备请求(应用层加密与签名)

在 HTTPS 基础上,对接口参数进行额外加密和签名,增强安全性。

  • 请求参数(原始)
json 复制代码
{
  "userId": "1001",
  "idCard": "110101199001011234",
  "amount": "5000.00",
  "timestamp": "1731123456789",
  "nonce": "abc123xyz"
}
  • 使用算法
    SM4:加密敏感字段(如 idCard、amount)
    SM3:计算待签名数据的摘要
    SM2:对摘要进行签名,生成数字签名
  • 实现步骤

(1)选择需加密的敏感字段

json 复制代码
"encryptedData": {
  "idCard": "110101199001011234",
  "amount": "5000.00"
}

(2)使用 SM4 加密敏感数据(CBC 模式 + 随机 IV)

客户端和服务端预先协商或通过安全通道分发一个 SM4 会话密钥(可用于一次请求或多请求),使用 SM4-CBC 加密 encryptedData,得到密文(Base64 编码)。

json 复制代码
"cipherText": "Base64编码的SM4密文"

实现过程如下。

先通过SM4-CBC加密算法得到二进制密文,然后进行Base64编码得到经过Base64编码的密文。

txt 复制代码
原始数据(JSON对象) → 转为字节数组 → SM4-CBC加密 → 二进制密文(byte[]) → Base64编码 → Base64字符串(可传输)

java实现示例

java 复制代码
// cipherBytes 是二进制数据,不能直接放在 JSON 中传输
byte[] cipherBytes = SM4_CBC_Encrypt(encryptedData, key, iv);
// 经过Base64编码后的密文
String cipherText = Base64.encode(cipherBytes);

(3)构造待签名字符串(拼接关键字段)

待签名字符串示例如下。

txt 复制代码
userId=1001&timestamp=1731123456789&nonce=abc123xyz&cipherText=Base64编码的SM4密文...

(4)使用 SM3 计算摘要

java 复制代码
digest = SM3(待签名字符串)

(5)使用客户端 SM2 私钥签名

java 复制代码
// 示例(伪代码)
byte[] derSignature = SM2_Sign(client_private_key, digest); // 已经是 DER 编码
String signature = Base64.encode(derSignature);        // 用于 JSON 传输

使用成熟的国密库(如 Bouncy Castle + 国密补丁、Hutool、GmSSL、SM-CRYPTO)时,签名方法通常会自动返回 DER 编码后的字节数组,只需再做一次 Base64 编码即可用于传输。

(6)构造最终请求体

json 复制代码
{
  "userId": "1001",
  "cipherText": "Base64编码的SM4密文",
  "timestamp": 1731123456789,
  "nonce": "abc123xyz",
  "signature": "Base64编码的SM2签名",
  "publicKey": "客户端SM2公钥(首次调用时提供,可缓存)"
}
3)、服务端验证与解密

服务端收到请求后,执行以下验证流程:

(1)基础校验

  • 检查 timestamp 是否过期(防重放攻击,如超过5分钟拒绝)
  • 检查 nonce 是否已使用(防重放)
  • 校验 userId 合法性

(2)获取客户端公钥

  • 若是首次调用,保存 publicKey
  • 否则从数据库或缓存中取出该用户的 SM2 公钥
  • 或者另行约定的方式

(3)重构待签名字符串(同客户端规则)

根据约定的签名字段、算法等要素进行构建待签名的字符串。

txt 复制代码
userId=1001&timestamp=...&nonce=...&cipherText=...

(4)使用 SM3 计算摘要

java 复制代码
digest_server = SM3(重构字符串)

(5)使用客户端公钥验证 SM2 签名

java 复制代码
isValid = SM2_Verify(client_public_key, digest_server, signature)
  • 若验证失败 → 拒绝请求(可能被篡改或伪造)

(6)使用 SM4 解密数据

  • 使用预共享的 SM4 密钥解密 cipherText
  • 得到原始敏感数据(如 idCard, amount

(7)处理业务逻辑

  • 使用解密后的数据完成业务操作

(8)返回响应(可选加密)

  • 响应也可使用 SM4 加密,并附 SM2 签名,确保下行安全

三、SM2、SM3、SM4 使用环节总结

环节 使用算法 用途
1. 传输层安全 SM2 + SM3 + SM4 国密 HTTPS,保障通信通道安全
2. 敏感参数加密 SM4 对身份证、金额等字段加密
3. 数据完整性校验 SM3 计算请求参数摘要,防篡改
4. 身份认证与抗抵赖 SM2 客户端私钥签名,服务端公钥验签
5. 密钥协商 SM2(ECDH) 在 HTTPS 握手中协商会话密钥
6. 证书体系 SM2 + SM3 国密数字证书,SM3 用于证书指纹

四、关键技术点与建议

  1. SM4 密钥管理
    • 可通过国密 HSM(硬件安全模块)生成和存储
    • 支持定期轮换,提高安全性
  2. SM2 密钥对
    • 客户端需持有自己的 SM2 密钥对(可存储在 U盾、TEE 或安全容器中)
    • 服务端需维护客户端公钥白名单
  3. 时间戳与随机数(nonce)
    • 必须使用,防止重放攻击
  4. 开发库推荐
    • Java:Bouncy Castle + 国密补丁、Hutool、Lang-Crypto
    • 前端:sm-crypto(JavaScript 国密库)
  5. 合规要求
    • 通过国家密码管理局的商用密码应用安全性评估(密评)
    • 使用经认证的密码产品(如国密 U盾、HSM)

五、总结

要实现一个国密合规的 HTTPS 接口调用系统,应采用"双层防护"策略:

第一层:国密 HTTPS

使用 SM2/SM3/SM4 构建安全传输通道,防止窃听。

第二层:应用层加密 + 签名

使用 SM4 加密敏感参数,SM3 + SM2 实现防篡改和身份认证。

这样既能满足高安全性要求,又能通过国家合规审查,适用于金融、政务、医疗等高敏感场景。

不能直接对原始消息进行签名,而是先对消息计算 SM3 摘要,再对摘要值进行签名。

原因1:性能效率(Efficiency)

  • 问题:如果直接对一个大文件(比如几MB的网页内容)进行 SM2 签名,计算量极大,速度慢。
  • 解决 :使用 SM3 将任意长度的数据压缩成 固定 256 位(32 字节) 的摘要,SM2 只需对这 32 字节签名,极大提升效率。

原因2:安全性(Security)

(1)防止"长度扩展攻击"等潜在风险

  • 直接对原始数据签名可能暴露结构信息,增加被攻击的风险。
  • 使用哈希函数(SM3)作为"中介",可以隔离原始数据与签名过程,增强安全性。

(2)SM2 算法设计要求输入为固定长度

  • SM2 基于椭圆曲线数学运算,其签名算法要求输入是一个固定长度的整数(通常是哈希值)。
  • 如果直接输入可变长度的原始消息,会导致算法不稳定或不安全。

国密标准 GM/T 0009-2012《SM2密码算法使用规范》明确规定

"在使用 SM2 进行数字签名时,应先使用 SM3 杂凑算法对消息进行摘要处理,然后对摘要值进行签名。"

原因3:完整性验证的标准化流程

在 HTTPS 握手过程中,数字证书和握手消息的签名验证流程如下:

复制代码
发送方(服务器):
1. 计算握手消息的摘要:digest = SM3(handshake_messages)
2. 使用私钥对摘要签名:signature = SM2_Sign(private_key, digest)
3. 发送签名值和原始消息

接收方(浏览器):
1. 接收到消息后,重新计算摘要:digest' = SM3(received_messages)
2. 使用服务器公钥验证签名:SM2_Verify(public_key, digest', signature)
3. 如果验证通过 → 说明消息未被篡改,且来源可信

这个流程中,SM3 保证了数据完整性,SM2 保证了身份认证和不可否认性,二者缺一不可。

接口调用防重放设计

使用 防重放随机数(Nonce) + 时间戳(Timestamp) 是最常用且有效的防御手段。

一、核心原理:Nonce + Timestamp

参数 作用
timestamp 请求时间戳(毫秒级),用于判断请求是否过期
nonce 一次性随机数,确保每次请求唯一

规则 :服务端只接受在一定时间窗口内(如5分钟)且 nonce 未被使用过的请求。

二、完整实现流程

步骤1:客户端生成请求

json 复制代码
{
  "userId": "1001",
  "amount": "100.00",
  "timestamp": 1731123456789,        // 当前时间毫秒
  "nonce": "aB3x9Kl2mNpQrStUvWzY",   // 随机字符串
  "signature": "MEUCIQD..."         // 对 (timestamp + nonce + ...) 签名
}

nonce 生成建议:

  • 长度:16~32位
  • 字符集:大小写字母 + 数字(避免特殊字符)
  • 生成方式:
    • 使用安全随机数生成器(如 Java 的 SecureRandom
    • 或使用 UUID 截取(去掉 - 后取部分)
java 复制代码
// Java 示例
String nonce = new BigInteger(130, new SecureRandom()).toString(36).substring(0, 20);

步骤2:服务端验证流程

txt 复制代码
收到请求 → 校验 timestamp → 检查 nonce 是否已使用 → 记录 nonce → 验证签名 → 处理业务
(1)校验时间戳(防过期)
java 复制代码
long currentTime = System.currentTimeMillis();
long requestTime = request.getTimestamp();

if (Math.abs(currentTime - requestTime) > 300_000) {  // 5分钟 = 300,000ms
    throw new SecurityException("请求已过期");
}

可接受范围通常设为 ±5分钟,避免因客户端时钟偏差导致误判。

(2)检查 nonce 是否已使用(防重放)
  • 使用 Redis 缓存已使用的 nonce
  • 设置过期时间略大于时间窗口(如 6 分钟)
java 复制代码
String key = "nonce:" + request.getUserId() + ":" + request.getNonce();

Boolean exists = redis.set(key, "1", Duration.ofMinutes(6));
if (!exists) {
    throw new SecurityException("重复请求,nonce 已使用");
}

SET key value EX 360 NX 命令可原子性完成"设置 + 过期 + 不存在才设"

(3)记录 nonce 并继续后续验证(如签名)

只有通过 timestampnonce 验证后,才进行 SM2 签名验证等耗时操作,防止被恶意刷接口。


三、关键设计要点

要点 说明
1. nonce 必须全局唯一 同一用户短时间内不能重复,建议结合用户ID做 key
2. 使用 Redis 缓存 nonce 高性能、自动过期,适合分布式系统
3. 过期时间 > 时间窗口 如时间窗口 5 分钟,nonce 缓存 6 分钟,防止临界问题
4. nonce 参与签名 必须将 noncetimestamp 加入签名原文,防止被篡改
5. 不依赖客户端 IP 移动端 IP 经常变化,不可靠

四、签名原文构造示例(含 nonce 和 timestamp)

txt 复制代码
userId=1001&
timestamp=1731123456789&
nonce=aB3x9Kl2mNpQrStUvWzY&
cipherText=...

然后对上述字符串进行 SM3 摘要 ,再用 SM2 私钥签名

如果攻击者修改 noncetimestamp,签名将验证失败。


五、可选增强方案

方案1:递增序列号(Sequence Number)

  • 客户端维护一个递增的 seq(从1开始)
  • 服务端记录每个用户的最新 seq
  • 每次请求 seq 必须比上次大
  • 优点:绝对防重放
  • 缺点:需持久化状态,客户端断线重连需同步

方案2:挑战-应答机制(Challenge-Response)

  • 服务端先返回一个 challenge(随机数)
  • 客户端在请求中包含该 challenge
  • 服务端验证后作废
  • 适用于高安全场景(如金融交易)

六、总结:防重放随机数实现要点

步骤 实现方式
1. 客户端生成 nonce = 安全随机字符串(16-32位),timestamp = 当前毫秒时间
2. 加入请求 noncetimestamp 放入请求参数
3. 参与签名 将两者加入签名原文,防止篡改
4. 服务端校验 检查时间窗口(±5分钟),检查 nonce 是否已使用(Redis)
5. 记录 nonce 使用 Redis 缓存 nonce,TTL = 6分钟

最佳实践口诀:"一户一 nonce,时效五分钟,Redis 记录,签名保完整"

通过 nonce + timestamp + Redis + 签名 四重防护,可有效抵御接口重放攻击,满足国密合规和高安全系统要求。

相关推荐
Web3_Daisy6 小时前
从透明到可控:链上换仓与资产路径管理的下一阶段
人工智能·安全·web3·区块链·比特币
金仓拾光集6 小时前
金仓数据库平替MongoDB:医共体数据互通的高效安全之道
数据库·安全·mongodb·kingbase·kingbasees·数据库平替用金仓·金仓数据库
知花实央l6 小时前
【Web应用安全】SQLmap实战DVWA SQL注入(从环境搭建到爆库,完整步骤+命令解读)
前端·经验分享·sql·学习·安全·1024程序员节
龙须草AI笔记8 小时前
N8N系列:新手课程,本地 N8N 不安全?Windows 下 HTTPS 配置指南,新手也能看懂
windows·安全·https·入门教程·ai技术·ai工具·n8n
cqwuliu9 小时前
通过nginx+openssl自签名证书部署https应用并解决不安全问题
nginx·安全·https
今日说"法"10 小时前
Rust 内存泄漏的检测与防范:超越安全的实践指南
java·安全·rust
NewCarRen19 小时前
整合STPA、ISO 26262与SOTIF的自动驾驶安全需求推导与验证
人工智能·安全·自动驾驶·预期功能安全
芯盾时代20 小时前
低空经济网络安全体系
安全·web安全
鹿鸣天涯20 小时前
关于进一步做好网络安全等级保护有关工作的问题释疑-【二级以上系统重新备案】、【备案证明有效期三年】
网络·安全·web安全