← 返回手册首页

请求签名详解

HMAC-SHA256 各语言示例与排错

请求签名

为防止请求被篡改与重放攻击,所有 /openapi/** 接口都要求带 HMAC-SHA256 签名。

StringToSign

签名的输入字符串由 3 段组成,\n(0x0A,单个换行字符)拼接

METHOD\nPATH\nTIMESTAMP
  • METHOD:大写 HTTP 方法,如 GETPOST
  • PATH:纯 path 部分(不含 query不含 host
  • TIMESTAMP:毫秒级 Unix 时间戳(与服务器时钟相差不超过 5 分钟)

例如调用 GET /openapi/v2/section/list?pageNum=1&pageSize=10,TIMESTAMP=1735000000000,StringToSign 为:

GET
/openapi/v2/section/list
1735000000000

注意:query 参数不参与签名

签名计算

signature = Base64( HMAC_SHA256( secret_key_bytes, string_to_sign_bytes ) )

字符集统一使用 UTF-8。

请求头

将上面计算的 signature 放到 X-Signature 请求头里,并同时携带 X-API-KeyX-Timestamp

X-API-Key: abcdef1234567890abcdef1234567890
X-Timestamp: 1735000000000
X-Signature: KvA0HwY3UCBYLfHV9Tw...QwO=

多语言示例

cURL + OpenSSL

API_KEY="your-api-key"
SECRET="your-secret-key"
TS=$(date +%s%3N)
METHOD="GET"
PATH_="/openapi/v2/section/list"

STRING_TO_SIGN=$(printf "%s\n%s\n%s" "$METHOD" "$PATH_" "$TS")
SIG=$(printf '%s' "$STRING_TO_SIGN" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)

curl "https://api.example.com${PATH_}" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG"

JavaScript (浏览器 Web Crypto / Node.js v15+)

async function sign(secret, method, path, timestamp) {
  const stringToSign = `${method}\n${path}\n${timestamp}`
  const enc = new TextEncoder()
  const key = await crypto.subtle.importKey(
    'raw',
    enc.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )
  const sig = await crypto.subtle.sign('HMAC', key, enc.encode(stringToSign))
  return btoa(String.fromCharCode(...new Uint8Array(sig)))
}

const ts = Date.now()
const signature = await sign(SECRET, 'GET', '/openapi/v2/section/list', ts)

Java (JDK 11+)

String stringToSign = method + "\n" + path + "\n" + timestamp;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] raw = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(raw);

Python 3

import hmac, hashlib, base64

string_to_sign = f"{method}\n{path}\n{timestamp}"
sig = base64.b64encode(
    hmac.new(secret.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256).digest()
).decode()

Go

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
)

stringToSign := fmt.Sprintf("%s\n%s\n%d", method, path, timestamp)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

失败原因排查

错误码 名称 排查建议
200301 签名验证失败 StringToSign 拼接 / 算法 / Base64 编码任一不匹配。先在沙箱里跑一遍对比。
200302 时间戳过期 与服务器时间相差 > 5min。检查本地时钟,必要时改用 NTP 同步。
200001 缺少 API Key 漏带 X-API-Key 请求头。

建议:开发时打开 在线沙箱,让浏览器替你完成签名,先确认端点能调通再写代码。

常见坑(请先自查)

  • 把 query 串拼进了 PATHPATH 只取 ? 之前的纯路径,query 参数不参与签名。
  • 用了 hex 而不是 Base64:签名结果必须是 Base64( HMAC-SHA256(...) ),不是十六进制字符串。
  • 密钥用反了:HMAC 密钥是 Secret KeyX-API-Key 头里放的才是 API Key。
  • 换行符被转义:StringToSign 里的分隔符是真实换行字符 \n(0x0A),不是字面的反斜杠加 n(\ + n)两个字符。
  • 时间戳单位错X-Timestamp 与签名里的 TIMESTAMP 必须是同一个毫秒级值,且两处一致。
  • 字符集不一致:StringToSign 与密钥都按 UTF-8 取字节,再做 HMAC。
  • PATH 末尾斜杠 / 大小写:要与实际请求行里的路径完全一致。

逐条核对仍不通过时,在 在线沙箱 调同一接口,对比沙箱生成的 StringToSign 与签名值,定位差异点。