HMAC-SM3 签名指南

安全认证解决方案 · 为第三方调用者准备的签名实现规范

签名流程概览

1
准备数据
2
构建待签名字符串
3
计算 HMAC-SM3
4
Base64 编码
5
构造请求

详细步骤说明

1. 准备必需数据

参数 说明
clientId 服务端分配的客户端标识 (例: your_client_id)
secret 服务端分配的明文密钥(仅用于签名计算,禁止传输!)
timestamp 当前毫秒级Unix时间戳(自1970-01-01 00:00:00 GMT的毫秒数)

2. 构建待签名字符串

规则:

示例:

clientId=your_client_id×tamp=1678886400123

3. HMAC-SM3 计算

算法规范:

4. Base64 编码

将 HMAC-SM3 结果字节数组转换为 Base64 字符串

5. 请求头设置

X-Client-Id: your_client_id
X-Timestamp: 1678886400123
X-Signature: {生成的Base64签名}

代码示例

时间戳生成

多语言实现
// Java
String timestamp = String.valueOf(System.currentTimeMillis());
# Python
import time
timestamp = str(int(time.time() * 1000))
// JavaScript
const timestamp = String(Date.now());
// PHP
$timestamp = (string)floor(microtime(true) * 1000);
// Go
import ("time"; "strconv")
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)

HMAC-SM3 实现

Java (BouncyCastle)
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.Base64;

public class HmacSm3Example {
    public static String generateSignature(String secret, String source) throws Exception {
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        byte[] dataBytes = source.getBytes(StandardCharsets.UTF_8);
        
        HMac hmac = new HMac(new SM3Digest());
        hmac.init(new KeyParameter(keyBytes));
        hmac.update(dataBytes, 0, dataBytes.length);
        
        byte[] result = new byte[32]; // SM3输出长度为32字节
        hmac.doFinal(result, 0);
        return Base64.getEncoder().encodeToString(result);
    }
}
Python (gmssl)
from gmssl import sm3, func
import hmac
import base64

def hmac_sm3_sign(secret: bytes, message: bytes) -> str:
    # 自定义HMAC-SM3实现
    block_size = 64  # HMAC block size for SM3
    if len(secret) > block_size:
        secret = sm3.sm3_hash(secret).encode()
    if len(secret) < block_size:
        secret += b'\x00' * (block_size - len(secret))
    
    o_key_pad = bytes([0x5c ^ b for b in secret])
    i_key_pad = bytes([0x36 ^ b for b in secret])
    
    inner_hash = sm3.sm3_hash(i_key_pad + message)
    return sm3.sm3_hash(o_key_pad + bytes.fromhex(inner_hash))
    
# 使用示例
signature = hmac_sm3_sign(
    secret = b"your_plaintext_secret",
    message = "clientId=test×tamp=123456789".encode()
)
print(base64.b64encode(bytes.fromhex(signature)).decode())

重要注意事项

必须遵守以下规则:
  1. 密钥仅用于签名,禁止在请求体/URL中传输
  2. 时间戳误差需在服务端窗口期内(通常±5分钟)
  3. 严格按字母顺序排列参数
  4. 使用完整的毫秒级时间戳(13位数字)
  5. 测试时与服务端保持时钟同步

请求示例

POST /api/v1/endpoint HTTP/1.1
Host: example.com
X-Client-Id: your_client_id
X-Timestamp: 1678886400123
X-Signature: 7H3z5q9O8k2F+vlSdYrXyLmNjG6PbQwKcEi4AaW1=

{"data": "example payload"}