HTTPS接口国密安全设计-示例
HTTPS接口国密安全设计
一、需求
1、总体安全需求
接口需要使用国密算法进行安全传输、防重放、防篡改和防伪造,敏感字段加密。
敏感字段是idCard和amount。
2、场景设定
客户端调用服务端接口
- 客户端(App 或前端)向服务端(API 接口)发送请求。
- 请求包含敏感参数(如用户身份证号、金额等),需加密。
- 接口需防重放、防篡改、防伪造。
3、原始接口参数
{"userId": "1001","idCard": "110101199001011234","amount": "5000.00","timestamp": "1731123456789","nonce": "abc123xyz"
}
二、设计与实现步骤
简单来说客户端实现
-
将需要加密的字段通过sm4加密,然后进行base64编码
-
将报文通过sm3进行报文摘要计算
-
将报文摘要进行签名,然后进行base64编码
-
将base64编码后的字符串(可选的公钥)作为参数
-
完成请求参数构造
实现简单代码示例如下。
// 原始报文
{"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×tamp=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解密
-
正常业务处理
实现简单代码示例如下。
// 原始报文{"userId": "1001","cipherText": "Base64编码的SM4密文",// 来自于第一步"timestamp": 1731123456789,"nonce": "abc123xyz","signature": "Base64编码的SM2签名", // 来自于第三步"publicKey": "客户端SM2公钥(首次调用时提供,可缓存)" // 一般是约定共享方式或作为参数传递
}// 0、在进行报文sm3摘要计算之前可能会做些基础性的校验,比如防重放等// 1、将报文通过sm3进行摘要计算
待签名字符串=“userId=1001×tamp=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 基础上,对接口参数进行额外加密和签名,增强安全性。
- 请求参数(原始)
{"userId": "1001","idCard": "110101199001011234","amount": "5000.00","timestamp": "1731123456789","nonce": "abc123xyz"
}
- 使用算法
SM4:加密敏感字段(如 idCard、amount)
SM3:计算待签名数据的摘要
SM2:对摘要进行签名,生成数字签名 - 实现步骤
(1)选择需加密的敏感字段
"encryptedData": {"idCard": "110101199001011234","amount": "5000.00"
}
(2)使用 SM4 加密敏感数据(CBC 模式 + 随机 IV)
客户端和服务端预先协商或通过安全通道分发一个 SM4 会话密钥(可用于一次请求或多请求),使用 SM4-CBC 加密 encryptedData,得到密文(Base64 编码)。
"cipherText": "Base64编码的SM4密文"
实现过程如下。
先通过SM4-CBC加密算法得到二进制密文,然后进行Base64编码得到经过Base64编码的密文。
原始数据(JSON对象) → 转为字节数组 → SM4-CBC加密 → 二进制密文(byte[]) → Base64编码 → Base64字符串(可传输)
java实现示例
// cipherBytes 是二进制数据,不能直接放在 JSON 中传输
byte[] cipherBytes = SM4_CBC_Encrypt(encryptedData, key, iv);
// 经过Base64编码后的密文
String cipherText = Base64.encode(cipherBytes);
(3)构造待签名字符串(拼接关键字段)
待签名字符串示例如下。
userId=1001×tamp=1731123456789&nonce=abc123xyz&cipherText=Base64编码的SM4密文...
(4)使用 SM3 计算摘要
digest = SM3(待签名字符串)
(5)使用客户端 SM2 私钥签名
// 示例(伪代码)
byte[] derSignature = SM2_Sign(client_private_key, digest); // 已经是 DER 编码
String signature = Base64.encode(derSignature); // 用于 JSON 传输
使用成熟的国密库(如 Bouncy Castle + 国密补丁、Hutool、GmSSL、SM-CRYPTO)时,签名方法通常会自动返回 DER 编码后的字节数组,只需再做一次 Base64 编码即可用于传输。
(6)构造最终请求体
{"userId": "1001","cipherText": "Base64编码的SM4密文","timestamp": 1731123456789,"nonce": "abc123xyz","signature": "Base64编码的SM2签名","publicKey": "客户端SM2公钥(首次调用时提供,可缓存)"
}
3)、服务端验证与解密
服务端收到请求后,执行以下验证流程:
(1)基础校验
- 检查
timestamp是否过期(防重放攻击,如超过5分钟拒绝) - 检查
nonce是否已使用(防重放) - 校验
userId合法性
(2)获取客户端公钥
- 若是首次调用,保存
publicKey - 否则从数据库或缓存中取出该用户的 SM2 公钥
- 或者另行约定的方式
(3)重构待签名字符串(同客户端规则)
根据约定的签名字段、算法等要素进行构建待签名的字符串。
userId=1001×tamp=...&nonce=...&cipherText=...
(4)使用 SM3 计算摘要
digest_server = SM3(重构字符串)
(5)使用客户端公钥验证 SM2 签名
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 用于证书指纹 |
四、关键技术点与建议
- SM4 密钥管理
- 可通过国密 HSM(硬件安全模块)生成和存储
- 支持定期轮换,提高安全性
- SM2 密钥对
- 客户端需持有自己的 SM2 密钥对(可存储在 U盾、TEE 或安全容器中)
- 服务端需维护客户端公钥白名单
- 时间戳与随机数(nonce)
- 必须使用,防止重放攻击
- 开发库推荐
- Java:Bouncy Castle + 国密补丁、Hutool、Lang-Crypto
- 前端:sm-crypto(JavaScript 国密库)
- 合规要求
- 通过国家密码管理局的商用密码应用安全性评估(密评)
- 使用经认证的密码产品(如国密 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 保证了身份认证和不可否认性,二者缺一不可。
下一篇介绍如何简单实现防重放
