Skip to content

请求签名

1. 鉴权方式说明

所有 API 请求 必须 使用 基于请求级数字签名的鉴权机制

  • 算法:ECDSA(ES256,P-256 + SHA-256)
  • 私钥生成:openssl ecparam -name prime256v1 -genkey -noout -out priKey.pem
  • 公钥生成:openssl ec -in priKey.pem -pubout -out pubKey.pem

2. 必须携带的 HTTP Header

所有请求 必须包含以下 Header

Header说明
X-Merchant-Id商户CNPJ(纯数字,不包含".,-"等符号)
X-Timestamp请求时间戳(Unix 秒)
X-Nonce随机字符串(防重放)
Digest请求体摘要
Authorization签名信息
Authorization.keyId签名的密钥标识,用于定位验签公钥

3. Digest 计算规则

Digest 用于将请求体纳入签名范围。

格式

Digest: SHA-256=<Base64(SHA256(body_bytes))>

规则

  • POST / PUT / PATCH:对原始 HTTP 请求体字节计算摘要
  • GET / DELETE:对空字节串计算摘要
  • 不允许对 JSON 重新序列化后再计算 Digest

4. 签名内容(Canonical String)

签名 必须且仅覆盖 以下 4 项,顺序固定:

  1. (request-target)
  2. x-timestamp
  3. x-nonce
  4. digest

(request-target) 格式

(request-target): <http-method小写> <path>[?<规范化query>]

示例:

(request-target): post /v1/orders
(request-target): get /v1/orders?order_id=123

Canonical String 格式规则

  • 每行格式固定为:

    <字段名>:<单个空格><字段值>
  • 使用 \n(LF)换行

  • 不允许多余空格或空行

示例

(request-target): post /v1/orders
x-timestamp: 1738123456
x-nonce: a9f3c1d47e8b9a2c
digest: SHA-256=Base64DigestValue

5. Authorization Header

格式

Authorization: Signature keyId="<merchant_id>",alg="ES256",headers="(request-target) x-timestamp x-nonce digest",signature="<Base64签名>"

说明

  • 使用商户 私钥 对 Canonical String 进行签名
  • 平台使用对应 公钥 进行验签

6. 防重放规则

平台将执行以下校验:

  • 时间戳校验X-Timestamp 与服务器时间差 ≤ 300 秒
  • Nonce 校验(merchant_id, nonce) 在有效时间窗口内 不可重复

7. 关键约束(请务必遵守)(必读)

  • (request-target) 必须参与签名,不可省略
  • Header 名在签名时 全部使用小写
  • 冒号后 必须是单个空格key: value
  • Canonical String 必须 逐字节一致
  • Authorization 中的 signature 不是单独使用的 Base64 值
  • 必须结合 keyId / alg / headers 才能完成正确验签
  • (request-target) 必须参与签名,不可省略
  • Digest 与 Authorization 必须同时校验

8. Authorization 生成流程

Authorization 的生成过程分为 4 个固定步骤

Step 1:准备请求体摘要(Digest)

示例请求体(最终发送的原始 JSON 文本):

json
{"order_no":"A10001","amount":100,"currency":"CNY"}

生成 Digest:

bodyBytes  = UTF8_BYTES('{"order_no":"A10001","amount":100,"currency":"CNY"}')
digestB64  = Base64(SHA256(bodyBytes))
Digest     = "SHA-256=" + digestB64

Step 2:构造 Canonical Signing String

(request-target): post /v1/orders
x-timestamp: 1738123456
x-nonce: a9f3c1d47e8b9a2c
digest: SHA-256=Base64DigestValue

Canonical String 必须逐字节一致:

  • 冒号后单个空格
  • 使用 \n(LF)换行
  • 字段顺序固定

Step 3:生成数字签名(ES256)

hashBytes   = SHA256(UTF8_BYTES(signingString))
signature   = ECDSA_SIGN_DER(privateKey, hashBytes)
signatureB64 = Base64(signature)

Step 4:生成 Authorization

Authorization =
  'Signature ' +
  'keyId="' + merchantId + '",' +
  'alg="ES256",' +
  'headers="(request-target) x-timestamp x-nonce digest",' +
  'signature="' + signatureB64 + '"'

9. Authorization 验证流程

在收到请求后,按以下流程进行验证:

Step 1:校验 Digest

expectedDigest =
  "SHA-256=" + Base64(SHA256(HTTP_RAW_BODY_BYTES))

if Digest != expectedDigest:
    reject(DIGEST_MISMATCH)

Step 2:重建 Canonical Signing String

(request-target): post /v1/orders
x-timestamp: <X-Timestamp>
x-nonce: <X-Nonce>
digest: <Digest>

Step 3:验签 Authorization

publicKey  = LOOKUP_PUBLIC_KEY(keyId)
hashBytes  = SHA256(UTF8_BYTES(signingString))
ok = ECDSA_VERIFY_DER(publicKey, hashBytes, Base64Decode(signature))

if ok != true:
    reject(SIGNATURE_INVALID)