Skip to content

鉴权与签名

除回调接收端外,所有 Open API 请求都需要以下 Header:

Header说明
Dujiao-Next-Api-Key用户生成的 API Key
Dujiao-Next-TimestampUnix 秒级时间戳
Dujiao-Next-SignatureHMAC-SHA256 签名,小写十六进制
Content-Typeapplication/json

签名串

严格按照以下四行拼接:

text
{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_MD5}
部分规则
METHOD大写 HTTP 方法,例如 GETPOST
PATH仅请求路径,不含域名和查询参数
TIMESTAMP与请求头时间戳完全一致
BODY_MD5实际请求体字节的 MD5,小写十六进制

无请求体时,MD5 固定为:

text
d41d8cd98f00b204e9800998ecf8427e

API Secret 如何参与签名

API_SECRET 不是直接拼接到签名串中,而是先转换为 UTF-8 字节,并作为 HMAC-SHA256 的密钥:

text
secret_bytes = UTF8(API_SECRET)
message_bytes = UTF8(sign_string)
signature_bytes = HMAC-SHA256(key=secret_bytes, message=message_bytes)
signature = HEX_LOWER(signature_bytes)

完整关系:

text
API_SECRET
  → UTF-8 bytes
  → 作为 HMAC-SHA256 key

METHOD + PATH + TIMESTAMP + BODY_MD5
  → sign_string
  → UTF-8 bytes
  → 作为 HMAC-SHA256 message

HMAC 结果
  → 小写十六进制
  → Dujiao-Next-Signature

不要先对 API_SECRET 做 MD5 或 SHA256。MD5 只用于请求体 bodyAPI_SECRET 原始 UTF-8 字节直接作为 HMAC key。

Python

python
import hashlib
import hmac
import time

method = "GET"
path = "/api/v1/upstream/products"
timestamp = str(int(time.time()))
body = b""

body_md5 = hashlib.md5(body).hexdigest()
sign_string = f"{method}\n{path}\n{timestamp}\n{body_md5}"
secret_bytes = API_SECRET.encode("utf-8")
message_bytes = sign_string.encode("utf-8")
signature = hmac.new(
    secret_bytes,
    message_bytes,
    hashlib.sha256,
).hexdigest()

Go

go
bodyMD5 := md5.Sum(body)
signString := strings.Join([]string{
    method,
    path,
    timestamp,
    hex.EncodeToString(bodyMD5[:]),
}, "\n")

secretBytes := []byte(apiSecret)
messageBytes := []byte(signString)
mac := hmac.New(sha256.New, secretBytes)
mac.Write(messageBytes)
signature := hex.EncodeToString(mac.Sum(nil))

容易出错的地方

  1. /products?page=2 签名时的 PATH 仍是 /api/v1/upstream/products,不包含查询字符串。
  2. POST JSON 必须先序列化成最终发送的字节,再计算 MD5;签名后不要重新格式化 JSON。
  3. 时间戳只允许约 ±60 秒偏差,服务器必须保持时间同步。
  4. 签名和 MD5 都使用小写十六进制。
  5. 不要把 Secret 放进 URL、浏览器脚本或日志。

签名测试向量

text
method:    GET
path:      /api/v1/upstream/products
timestamp: 1700000000
body:      <empty>
secret:    test_secret

期望签名:

text
eebf4100af7232ab580df9acddc32784aff5257e14c96d776668c7855dcfb3a1

完整实现见 PythonGo

CheapEmail Open API Documentation