Skip to content
smallPict
免费开始

HMAC-SHA256 签名认证规范

如何通过密码学 HMAC-SHA256 签名算法对 SmallPict API 请求进行安全鉴权。

为了确保安全性,SmallPict API 全面采用 密码学 HMAC-SHA256 请求签名认证机制。

此标准在传输层提供三大核心安全保障:

  1. 载荷完整性 (Payload Integrity): 请求路径与 Body 内容在传输途中无法被恶意篡改。
  2. 防重放攻击 (Anti-Replay Attack): 严格的时间戳漂移窗口阻断被截获请求的重复利用。
  3. 零密钥泄露风险 (Zero Secret Exposure): 您的 Secret Key 永不在公网明文传输。

必需的 HTTP 请求头

每个受保护的 API 调用都必须在 HTTP Request Header 中包含以下 3 个字段:

HTTP
X-API-Key: sp_sdk_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0bX-Timestamp: 1716301234X-Signature: 3a9f8b2c4d6e8a0f1b3c5d7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a
请求头字段数据类型描述与取值规则
X-API-KeyString客户公开凭证(如 sp_sdk_..., sp_wp_..., sp_test_...)
X-TimestampInteger String发起请求时的 Unix 时间戳(秒级整数字符串)
X-SignatureHex String使用 Secret Key 计算得到的 64 位十六进制 HMAC-SHA256 签名

签名生成算法

步骤 1:构建规范待签名字符串 (String-To-Sign)

使用换行符 (\n) 将 HTTP 方法、请求路径、时间戳与 Body 哈希依次拼接:

TEXT
{HTTP_METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_SHA256_HEX}
  • {HTTP_METHOD}:大写的请求动词(如 POST、GET、DELETE)。
  • {PATH}:不带 Query 参数的精确请求 URI(如 /v1/optimize、/v1/quota)。
  • {TIMESTAMP}:与请求头 X-Timestamp 完全一致的当前时间戳。
  • {BODY_SHA256_HEX}:请求 Body 原始字节的 SHA-256 十六进制哈希。对于 GET 请求或无 Body 请求,请使用空字符串的哈希值:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。

步骤 2:计算 HMAC-SHA256 哈希

TEXT
Signature = hex(HMAC_SHA256(SecretKey, StringToSign))

官方多语言签名代码实现

Node.js (TypeScript)

TypeScript
import crypto from 'node:crypto';
export function signRequest(  method: string,  path: string,  secretKey: string,  bodyString: string = '') {  const timestamp = Math.floor(Date.now() / 1000).toString();  const bodyHash = crypto.createHash('sha256').update(bodyString).digest('hex');  const stringToSign = `${method.toUpperCase()}\n${path}\n${timestamp}\n${bodyHash}`;  const signature = crypto    .createHmac('sha256', secretKey)    .update(stringToSign)    .digest('hex');
  return { timestamp, signature };}

Python 3

Python
import hmacimport hashlibimport time
def sign_request(method: str, path: str, secret_key: str, body_bytes: bytes = b"") -> tuple[str, str]:    timestamp = str(int(time.time()))    body_hash = hashlib.sha256(body_bytes).hexdigest()    string_to_sign = f"{method.upper()}\n{path}\n{timestamp}\n{body_hash}"    signature = hmac.new(        secret_key.encode("utf-8"),        string_to_sign.encode("utf-8"),        hashlib.sha256    ).hexdigest()    return timestamp, signature

Go

Go
package main
import (	"crypto/hmac"	"crypto/sha256"	"encoding/hex"	"fmt"	"strconv"	"time")
func SignRequest(method, path, secretKey string, body []byte) (timestamp string, signature string) {	ts := strconv.FormatInt(time.Now().Unix(), 10)	h := sha256.Sum256(body)	bodyHash := hex.EncodeToString(h[:])	stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", method, path, ts, bodyHash)
	mac := hmac.New(sha256.New, []byte(secretKey))	mac.Write([]byte(stringToSign))	return ts, hex.EncodeToString(mac.Sum(nil))}

时间戳漂移校验窗口 (±300 秒)

为防范重放攻击,SmallPict 网关强制校验客户端时间戳与网关服务器标准时间的误差:

凡满足 |server_time - X-Timestamp| > 300 秒的请求,一律返回 401 Unauthorized (ERR_TIMESTAMP_DRIFT)。请确保发起请求的服务器开启了 NTP 时间自动校准。