Skip to content

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. 计算 signatureHMAC-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 如下:

json
{
  "external_player_id": "player1",
  "player_name": "player1_name",
  "currency_code": "CNY"
}

JCS canonicalization 后(key 按字典序排序、无多余空白):

json
{"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 的固定值:

text
KeyA = "{}"
KeyB = sha256("{}") =
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

TIP

多一个空格、换行、key 顺序不同,结果会完全不一样。

3. 制作 canonical request

将下列字段,作为 HMAC 的输入:

字段说明
<version>v1固定值
<bodyHash>KeyB第 2 步算出的 hex
<timestamp>时间戳(毫秒),例如 1718105400000;服务器允许与当前时间偏差 ±60 秒,超出返回认证失败
<nonce>每次请求需不同,必须为 UUID
<idempotencyKey>幂等键,让同一个操作重发不会重复生效,必须为 UUID

canonical request 最终格式:

text
v1<KeyB><timestamp><nonce><idempotencyKey>

TIP

每个字段间直接按上述顺序,不可加空格、不可少欄或換行,否则签名会不同。

4. 计算 signature

使用 api_secret 对 canonical request 计算 HMAC-SHA256,再 Base64 编码,得到 signature

text
signature = base64( HMAC_SHA256(api_secret, canonical_request) )

5. 加上 HTTP Headers 发送请求

所有 API(GET 与 POST)均需带入下列 Header:

Header说明
X-API-Keyapi_key
X-Signature第 4 步的 signature
X-Timestamp第 3 步的 <timestamp>
X-Nonce第 3 步的 <nonce>
X-Body-HashKeyB(即使是 GET 也必须带,值为 sha256("{}"))
X-Signature-Versionv1
X-Idempotency-Key第 3 步的 <idempotencyKey>
Content-Typeapplication/json(仅 POST 需要)

完整签名示例(可用于自测)

以下为一组真实可验证的数据,商户可逐步对照,确认自己的实现是否正确。

原始 Request Body

json
{
  "currencyCode": "USD",
  "externalPlayerId": "player123",
  "playerName": "player123"
}

步骤 1 — JCS 序列化(KeyA)

key 按字典序排列、无多余空白:

text
{"currencyCode":"USD","externalPlayerId":"player123","playerName":"player123"}

步骤 2 — Body Hash(KeyB)

SHA-256(KeyA) → 十六进制小写:

text
f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322

步骤 3 — Canonical Request

v1 + KeyB + timestamp + nonce + idempotencyKey,直接拼接:

text
v1f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e32217817550475420111f43e-ae1e-4608-a406-cb7bffe74fbed037c7a1-4238-425c-86e5-928fac930806

步骤 4 — Signature

HMAC-SHA256(api_secret, canonical request) → Base64:

text
VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA=

完整 Headers

Header
Content-Typeapplication/json
X-API-KeyI0fRgq1DV79L8b37m9LdHjtJQgNrcC1udH-LwXNxLhW2b2vXZzCPkGoLVDP3mww2
X-Timestamp1781755047542
X-Nonce0111f43e-ae1e-4608-a406-cb7bffe74fbe
X-Body-Hashf6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322
X-Signature-Versionv1
X-Idempotency-Keyd037c7a1-4238-425c-86e5-928fac930806
X-SignatureVoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA=

TIP

用于签名的 api_secret 为 fx5PaVEVJkgwEwT_jAtTrj1PJCDvaq6XqshiUcJSo78。如果你的 canonical request 或 signature 与以上不符,请逐步检查每一步的输出值。

BFX EXCHANGE · BE THE GAME CHANGER