请求签名
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 项,顺序固定:
(request-target)x-timestampx-noncedigest
(request-target) 格式
(request-target): <http-method小写> <path>[?<规范化query>]示例:
(request-target): post /v1/orders
(request-target): get /v1/orders?order_id=123Canonical String 格式规则
每行格式固定为:
<字段名>:<单个空格><字段值>使用
\n(LF)换行不允许多余空格或空行
示例
(request-target): post /v1/orders
x-timestamp: 1738123456
x-nonce: a9f3c1d47e8b9a2c
digest: SHA-256=Base64DigestValue5. 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=" + digestB64Step 2:构造 Canonical Signing String
(request-target): post /v1/orders
x-timestamp: 1738123456
x-nonce: a9f3c1d47e8b9a2c
digest: SHA-256=Base64DigestValueCanonical 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)