首页|接入文档
API v1

签名算法

API v1

签名算法

签名用于验证请求合法性,防止参数被篡改。当前 API 使用 HMAC-SHA256,密钥为软件的 App Secret

签名步骤

  1. 收集所有请求参数(不含 sign 字段本身),包括 Body 参数或 Query 参数
  2. 按参数名字典升序排列,拼接为 k1=v1&k2=v2&...kn=vn
  3. 拼接签名原文:HTTP方法 + Host + 接口路径 + 参数字符串
  4. 使用 App Secret 作为 HMAC 密钥,对签名原文计算 HMAC-SHA256,输出小写十六进制字符串

签名公式

// 伪代码
params = 所有参数(不含sign),按key升序排列
paramStr = params.map(k => k+"="+v).join("&")
raw = METHOD + host + path + paramStr
sign = HMAC_SHA256(app_secret, raw).toLowerCase()

Host 必须与实际请求 URL 的 host 一致,包含端口。例如本地开发环境通常是 localhost:3000

TypeScript 示例

import { createHmac, randomBytes } from "node:crypto";

function randomNonce() {
    return randomBytes(8).toString("hex");
}

function calcSign(
    method: string,
    host: string,
    path: string,
    params: Record<string, string | number>,
    appSecret: string,
) {
    const paramStr = Object.keys(params)
        .filter((key) => key !== "sign")
        .sort()
        .map((key) => `${key}=${params[key]}`)
        .join("&");

    const raw = `${method.toUpperCase()}${host}${path}${paramStr}`;
    return createHmac("sha256", appSecret).update(raw).digest("hex");
}

const params = {
    app_key: "your_app_key",
    card: "XXXX-XXXX-XXXX-XXXX",
    device_id: "device-001",
    nonce: randomNonce(),
    timestamp: Math.floor(Date.now() / 1000),
};

const sign = calcSign("POST", "ttqsoft.com", "/api/v1/card/login", params, "your_app_secret");

C# 示例

using System.Security.Cryptography;
using System.Text;

string CalcSign(string method, string host, string path,
    Dictionary<string, string> prms, string secret) {
    var sorted = prms
        .Where(kv => kv.Key != "sign")
        .OrderBy(kv => kv.Key)
        .Select(kv => $"{kv.Key}={kv.Value}");
    string paramStr = string.Join("&", sorted);
    string raw = method.ToUpper() + host + path + paramStr;
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(raw));
    return Convert.ToHexString(hash).ToLower();
}

Python 示例

import hmac, hashlib, time, secrets

def calc_sign(method, host, path, params, secret):
    items = sorted(
        [(k, v) for k, v in params.items() if k != "sign"],
        key=lambda x: x[0]
    )
    param_str = "&".join(f"{k}={v}" for k, v in items)
    raw = method.upper() + host + path + param_str
    return hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()

# 示例
params = {
    "app_key": "your_app_key",
    "card": "XXXX-XXXX-XXXX",
    "device_id": "device_001",
    "nonce": secrets.token_hex(8),
    "timestamp": int(time.time()),
}
params["sign"] = calc_sign("POST", "your.domain.com",
    "/api/v1/card/login", params, "your_app_secret")

注意事项

  • GET 请求的参数在 Query String 中;POST 请求的参数在 JSON Body 中
  • Host 为请求的域名(含端口,如有),例如 verify.example.com
  • App Secret 在开发者控制台获取,请勿泄露给最终用户