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 保证了身份认证和不可否认性,二者缺一不可。

下一篇介绍如何简单实现防重放

相关推荐
德迅云安全—珍珍2 分钟前
主机安全-德迅卫士
linux·服务器·安全
想用offer打牌6 分钟前
一站式了解跨域问题
网络协议·面试·架构
伊玛目的门徒22 分钟前
HTTP SSE 流式响应处理:调用腾讯 智能应用开发平台ADP智能体的 API
python·网络协议·http·腾讯智能体·adp·智能应用开发平台
2501_9388101124 分钟前
动态IP的使用方法
网络·网络协议·tcp/ip
Neolnfra37 分钟前
陇剑杯2021-wifi题目解析
网络·安全·web安全·网络安全·系统安全·密码学·csrf
无限大.1 小时前
为什么网站需要“域名“?——从 IP 地址到网址的演进
网络·网络协议·tcp/ip
wha the fuck4041 小时前
(渗透脚本)TCP创建连接脚本----解题----极客大挑战2019HTTP
python·网络协议·tcp/ip·网络安全·脚本书写
Upper9992 小时前
简单记录:TCP数据包的抓取--3次握手、4次挥手
网络·网络协议·tcp/ip
ZeroNews内网穿透2 小时前
Dify AI 结合ZeroNews 实现公网快速访问
网络·人工智能·网络协议·tcp/ip·安全·web安全
yBmZlQzJ2 小时前
内网穿透 + 域名解析:到底解决了什么核心问题?
运维·经验分享·网络协议·docker·容器