2.2 HMAC 验证流程
所有 API 请求须在 header 中带入加密签名,请按照以下 5 个步骤处理。文末提供完整签名示例,可用于验证自己的实现是否正确。
步骤总览
| 步骤 | 操作 | 产出 |
|---|---|---|
| 1. request body → JCS | 将 body 按 RFC 8785 规范排序序列化 | KeyA |
| 2. JCS → hash(JCS) | SHA-256(KeyA) → 十六进制小写字符串 | KeyB(bodyHash) |
| 3. 制作 canonical request | 将 version / bodyHash / timestamp / nonce / idempotencyKey 按序直接拼接 | canonical request |
| 4. 计算 signature | HMAC-SHA256(api_secret, canonical request) → Base64 编码 | signature |
| 5. 加上 HTTP Headers 发送 | 将 signature 及相关字段写入 HTTP Header 后发送 | — |
request body ──▶ JCS canonicalization (RFC 8785) ──▶ KeyA
KeyA ──▶ sha256(hex, 小写) ──▶ KeyB(bodyHash)
KeyB ──▶ canonical request(5 个字段,直接拼接)
canonical request ──▶ HMAC-SHA256(api_secret) ──▶ Base64 ──▶ signature
signature + headers ──▶ 发送请求1. request body → JCS
将 request body 转成 JSON 并按照 JCS canonicalization(RFC 8785) 排序得到 KeyA。
以 login 为例,request body 如下:
{
"external_player_id": "player1",
"player_name": "player1_name",
"currency_code": "CNY"
}JCS canonicalization 后(key 按字典序排序、无多余空白):
{"currency_code":"CNY","external_player_id":"player1","player_name":"player1_name"}TIP
当请求没有 body 时(例如所有 GET API),必须以 "{}" 作为 KeyA,不可使用空字符串 ""。否则会得到 code:10000 "authentication failed"。
2. JCS → hash(JCS)
将 KeyA 计算 SHA-256 哈希,转换为十六进制小写字符串(hex) 得到 KeyB。
例如空 body 的固定值:
KeyA = "{}"
KeyB = sha256("{}") =
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8aTIP
多一个空格、换行、key 顺序不同,结果会完全不一样。
3. 制作 canonical request
将下列字段,作为 HMAC 的输入:
| 字段 | 值 | 说明 |
|---|---|---|
| <version> | v1 | 固定值 |
| <bodyHash> | KeyB | 第 2 步算出的 hex |
| <timestamp> | 时间戳(毫秒),例如 1718105400000;服务器允许与当前时间偏差 ±60 秒,超出返回认证失败 | |
| <nonce> | 每次请求需不同,必须为 UUID | |
| <idempotencyKey> | 幂等键,让同一个操作重发不会重复生效,必须为 UUID |
canonical request 最终格式:
v1<KeyB><timestamp><nonce><idempotencyKey>TIP
每个字段间直接按上述顺序,不可加空格、不可少欄或換行,否则签名会不同。
4. 计算 signature
使用 api_secret 对 canonical request 计算 HMAC-SHA256,再 Base64 编码,得到 signature。
signature = base64( HMAC_SHA256(api_secret, canonical_request) )5. 加上 HTTP Headers 发送请求
所有 API(GET 与 POST)均需带入下列 Header:
| Header | 说明 |
|---|---|
| X-API-Key | api_key |
| X-Signature | 第 4 步的 signature |
| X-Timestamp | 第 3 步的 <timestamp> |
| X-Nonce | 第 3 步的 <nonce> |
| X-Body-Hash | KeyB(即使是 GET 也必须带,值为 sha256("{}")) |
| X-Signature-Version | v1 |
| X-Idempotency-Key | 第 3 步的 <idempotencyKey> |
| Content-Type | application/json(仅 POST 需要) |
完整签名示例(可用于自测)
以下为一组真实可验证的数据,商户可逐步对照,确认自己的实现是否正确。
原始 Request Body
{
"currencyCode": "USD",
"externalPlayerId": "player123",
"playerName": "player123"
}步骤 1 — JCS 序列化(KeyA)
key 按字典序排列、无多余空白:
{"currencyCode":"USD","externalPlayerId":"player123","playerName":"player123"}步骤 2 — Body Hash(KeyB)
SHA-256(KeyA) → 十六进制小写:
f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322步骤 3 — Canonical Request
v1 + KeyB + timestamp + nonce + idempotencyKey,直接拼接:
v1f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e32217817550475420111f43e-ae1e-4608-a406-cb7bffe74fbed037c7a1-4238-425c-86e5-928fac930806步骤 4 — Signature
HMAC-SHA256(api_secret, canonical request) → Base64:
VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA=完整 Headers
| Header | 值 |
|---|---|
| Content-Type | application/json |
| X-API-Key | I0fRgq1DV79L8b37m9LdHjtJQgNrcC1udH-LwXNxLhW2b2vXZzCPkGoLVDP3mww2 |
| X-Timestamp | 1781755047542 |
| X-Nonce | 0111f43e-ae1e-4608-a406-cb7bffe74fbe |
| X-Body-Hash | f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322 |
| X-Signature-Version | v1 |
| X-Idempotency-Key | d037c7a1-4238-425c-86e5-928fac930806 |
| X-Signature | VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA= |
TIP
用于签名的 api_secret 为 fx5PaVEVJkgwEwT_jAtTrj1PJCDvaq6XqshiUcJSo78。如果你的 canonical request 或 signature 与以上不符,请逐步检查每一步的输出值。