Skip to content

签名算法

所有需要鉴权的 API 请求都必须携带签名,平台通过签名验证请求的合法性和完整性。

算法概述

项目说明
算法HMAC-SHA256
密钥AppSecret
编码签名结果转为 Base64 字符串

签名步骤

第一步:拼接待签名字符串

将以下字段按顺序 直接拼接(无分隔符、无键名):

待签名字符串 = AppKey + Timestamp + Nonce + RequestBody
字段来源说明
AppKeyX-App-Key Header应用标识
TimestampX-Timestamp HeaderUnix 时间戳(秒级)
NonceX-Nonce Header随机字符串
RequestBodyHTTP 请求体GET 请求或无 Body 时为空字符串

第二步:使用 HMAC-SHA256 计算签名

sign = Base64( HMAC-SHA256( AppSecret, 待签名字符串 ) )

第三步:将签名放入请求头

X-Sign: {sign}

完整示例

参数准备

参数
AppKeyapp_test_001
AppSecretsecret_abc_123
Timestamp1710000000
Noncea1b2c3d4e5

GET 请求签名

请求:GET /open-api/merchant/info?id=1001

待签名字符串(无 Body,末尾为空):

app_test_0011710000000a1b2c3d4e5

签名计算:

sign = Base64(HMAC-SHA256("secret_abc_123", "app_test_0011710000000a1b2c3d4e5"))

POST 请求签名

请求:POST /open-api/order/create

请求体:

json
{"merchantId":1001,"storeId":2001,"totalAmount":29900}

待签名字符串:

app_test_0011710000000a1b2c3d4e5{"merchantId":1001,"storeId":2001,"totalAmount":29900}

签名计算:

sign = Base64(HMAC-SHA256("secret_abc_123", "app_test_0011710000000a1b2c3d4e5{\"merchantId\":1001,\"storeId\":2001,\"totalAmount\":29900}"))

各语言实现

Java

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;

public class OpenApiSigner {

    public static String sign(String appSecret, String appKey,
                              String timestamp, String nonce, String body) throws Exception {
        String dataToSign = appKey + timestamp + nonce + (body != null ? body : "");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hash);
    }

    public static void main(String[] args) throws Exception {
        String appKey = "app_test_001";
        String appSecret = "secret_abc_123";
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonce = UUID.randomUUID().toString().replace("-", "");
        String body = "{\"orderId\":\"20240301001\"}";

        System.out.println("X-Sign: " + sign(appSecret, appKey, timestamp, nonce, body));
    }
}

Python

python
import hmac
import hashlib
import base64
import time
import uuid

def sign(app_secret: str, app_key: str, timestamp: str, nonce: str, body: str = "") -> str:
    data_to_sign = app_key + timestamp + nonce + body
    signature = hmac.new(
        app_secret.encode("utf-8"),
        data_to_sign.encode("utf-8"),
        hashlib.sha256
    ).digest()
    return base64.b64encode(signature).decode("utf-8")

# 示例
app_key = "app_test_001"
app_secret = "secret_abc_123"
timestamp = str(int(time.time()))
nonce = uuid.uuid4().hex
body = '{"orderId":"20240301001"}'

print("X-Sign:", sign(app_secret, app_key, timestamp, nonce, body))

Node.js

javascript
const crypto = require('crypto');

function sign(appSecret, appKey, timestamp, nonce, body = '') {
  const dataToSign = appKey + timestamp + nonce + body;
  return crypto
    .createHmac('sha256', appSecret)
    .update(dataToSign, 'utf8')
    .digest('base64');
}

// 示例
const appKey = 'app_test_001';
const appSecret = 'secret_abc_123';
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomUUID().replace(/-/g, '');
const body = '{"orderId":"20240301001"}';

console.log('X-Sign:', sign(appSecret, appKey, timestamp, nonce, body));

cURL

bash
#!/bin/bash
APP_KEY="app_test_001"
APP_SECRET="secret_abc_123"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr -d '-')
BODY='{"orderId":"20240301001"}'

DATA_TO_SIGN="${APP_KEY}${TIMESTAMP}${NONCE}${BODY}"
SIGN=$(echo -n "$DATA_TO_SIGN" | openssl dgst -sha256 -hmac "$APP_SECRET" -binary | base64)

curl -X POST "https://api.example.com/open-api/order/create" \
  -H "Content-Type: application/json" \
  -H "X-App-Key: $APP_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Sign: $SIGN" \
  -d "$BODY"

常见问题

签名验证失败排查

  1. 拼接顺序:必须是 AppKey + Timestamp + Nonce + Body,顺序不能错
  2. 无分隔符:字段之间直接拼接,不要加 &= 或其他分隔符
  3. 编码方式:签名结果是 Base64 编码,不是 Hex
  4. Body 原文:POST 请求的 Body 必须是发送的原始 JSON 字符串,保持紧凑格式
  5. GET 请求:无 Body 时,待签名串末尾为空(即只有 AppKey + Timestamp + Nonce
  6. 字符编码:所有字符串使用 UTF-8 编码
  7. 时间戳:使用秒级时间戳,不是毫秒级
  8. AppSecret:确认使用正确的密钥,注意是否已被重置

商数通开放平台