基于 SM2/SM4 的签名与加密调用流程梳理
本文记录了某接口调用过程中,如何基于 SM2 签名 与 SM4 加密 完成数据请求的流程。为安全起见,涉及的密钥参数已做脱敏处理。
一、公共参数配置
private static String url = "http://xx.xx.xx.xx:8083/epc/api";
private static String appSecret = "****";
private static String appId = "****";
private static String publicKey = "****";
private static String privateKey = "****";
private static String path = "/fixmedins/hospRxDetlQuery";
二、SM2 签名流程
1. 构建 signstr
- 剔除字段:
signData、encData、extra - 将剩余参数排序
- 拼接
appSecret到最后的key字段
示例入参:
{
"certno": "612*****316",
"drCode": "D610****06954",
"fixmedinsCode": "H610****0077",
"fixmedinsName": "某某医院",
"hiRxno": "610700022**********45233982978",
"mdtrtId": "1274753890",
"patnName": "张三",
"pharCertType": "01",
"pharCertno": "21233121",
"pharChkTime": "2025-08-27 17:57:59",
"pharCode": "111",
"pharDeptCode": "A50.04",
"pharDeptName": "儿一科",
"pharName": "李四",
"pharProfttlCodg": "1",
"pharProfttlName": "执业药师",
"prscDrName": "王五",
"psnCertType": "01",
"rxTraceCode": "612*****261"
}
拼接后的 signstr:
appId=****&data={...}&encType=SM4&signType=SM2×tamp=1757326792488&version=1.0.0&key=****
2. 私钥转换
原始私钥(Base64 编码)需先转化为十六进制格式:
CommonUtil.byteArrayToHex(this.decoder.decode(privateKey));
示例转换结果(脱敏):
69ECB3F1F419A015******************************B4DDE79CB3D8899542
3. 执行签名
- 算法:
GMObjectIdentifiers.sm2sign_with_sm3 - 返回值为 Base64 编码签名字符串
示例签名结果:
j7hAVMocO14BPOli/bKxe6Md4d********************************yuPjhKnTLMZOMKI3kJngKfIUolg==
三、SM4 加密流程
1. 待加密的 data
仅取业务字段部分(如处方信息等 JSON)。
{
"pharDeptName": "儿一科",
"pharCertType": "01",
"psnCertType": "01",
"pharName": "李四",
"hiRxno": "610*****978",
"pharCertno": "21233121",
"pharCode": "111",
"pharDeptCode": "A50.04",
"drCode": "D610724006954",
"pharProfttlName": "执业药师",
"certno": "612****316",
"fixmedinsCode": "H61****077",
"patnName": "张三",
"prscDrName": "王五",
"rxTraceCode": "612897******48261",
"pharChkTime": "2025-08-27 17:57:59",
"pharProfttlCodg": "1",
"fixmedinsName": "某某医院",
"mdtrtId": "127*****90"
}
2. 生成 SM4 Key
SM4Util.encryptEcb(CommonUtil.stringToHexString(appID.substring(0, 16)), secret)
.substring(0, 16)
.toUpperCase()
.getBytes();
3. 执行加密
- 模式:
SM4/ECB/PKCS7Padding - 调用
Cipher cipher = generateEcbCipher(...); - 最终输出为十六进制字符串
示例加密结果(脱敏) :
f4064a4d7e1291ea1aef4fc8436a4a22...
...39ef651c08fb50eec96f23b184d2506
四、请求参数组装
最终请求中:
data字段需删除- 新增
encData(即 SM4 加密结果) - 搭配
signData(SM2 签名结果)
所有参数放入 TreeMap 后再发起请求。
五、总结
- SM2 用于签名,保证请求数据完整性与来源可靠。
- SM4 用于加密,保护敏感数据传输过程中的安全性。
- 请求参数需 排序 + 拼接 appSecret,生成签名字符串。
data字段仅参与签名,不直接传输;传输的是encData。
六、调用时序流程图
sequenceDiagram
participant Client as 客户端
participant SM2 as SM2签名模块
participant SM4 as SM4加密模块
participant Server as 服务端接口
Client->>Client: 构建业务参数(JSON)
Client->>Client: 剔除 signData/encData/extra
Client->>Client: 参数排序 + 拼接 appSecret = signstr
Client->>SM2: 使用私钥对 signstr 执行 SM2 签名
SM2-->>Client: 返回 signData(Base64)
Client->>SM4: 使用 appId/secret 生成 SM4 key
Client->>SM4: 对 data(JSON) 执行 SM4/ECB/PKCS7Padding 加密
SM4-->>Client: 返回 encData(Hex)
Client->>Client: TreeMap 组装请求参数
Note right of Client: 删除原 data,保留<br/>appId、timestamp、signData、encData 等
Client->>Server: 发送请求(url+path)
Server-->>Client: 返回处理结果(JSON)
七、签名与加密整体流程图
graph TD
A[开始] --> B[构建业务参数 JSON]
B --> C[剔除 signData / encData / extra 字段]
C --> D[参数排序]
D --> E[拼接 appSecret 得到 signstr]
E --> F1[SM2 签名]
F1 --> F2[生成 signData Base64]
D --> H[提取 data JSON]
H --> I[生成 SM4 Key 使用 appId 和 secret]
I --> J[SM4 加密 ECB PKCS7Padding]
J --> K[生成 encData Hex]
F2 --> L[TreeMap 组装参数]
K --> L
L --> M[删除 data 保留 signData 和 encData]
M --> N[发送请求 url+path]
N --> O[服务端接收并校验]
O --> P[返回结果 JSON]
P --> Q[结束]