API接入

幣安API簽名怎麼生成?HMAC-SHA256多語言實現

幣安 API HMAC-SHA256 簽名全解:演算法原理、引數序列化規則、URL 編碼細節、Ed25519 替代方案、Python/Node.js/Go/Rust/Java 多語言實現、常見簽名錯誤排查。

幣安 API 簽名的標準演算法是 HMAC-SHA256,原理是用 Secret Key 作為金鑰、查詢字串作為訊息,生成 64 位十六進位制字串作為 signature 引數附加到請求末尾。2023 年起幣安新增 Ed25519 非對稱簽名方案,安全性更高但使用率低。本文詳解演算法原理、5 種語言實現、8 個常見簽名錯誤的排查方法。未開通幣安賬號請先到 幣安官網 完成註冊;沒有賬號的可 免費註冊

一、HMAC-SHA256 的數學原理

HMAC(Hash-based Message Authentication Code)是基於雜湊的訊息認證碼,公式為:

HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))

其中:

  • K 是金鑰(Secret Key)
  • m 是訊息(查詢字串)
  • H 是 SHA256 雜湊函式
  • ipad = 0x36 重複 64 次
  • opad = 0x5C 重複 64 次
  • ⊕ 是異或,|| 是拼接

幣安具體流程

  1. 把所有引數按順序拼成 key1=value1&key2=value2 形式
  2. 用 Secret Key 作為 HMAC 金鑰、上述字串作為訊息
  3. 計算 SHA256 摘要,輸出 32 位元組
  4. 轉換為 64 位十六進位制字串

二、幣安簽名的 3 條引數規則

規則 1:引數順序必須與簽名一致

# 正確:簽名與請求 query 一致
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
signature = hmac_sha256("symbol=BTCUSDT&timestamp=1713027384562")
final_query = "symbol=BTCUSDT&timestamp=1713027384562&signature=" + signature

# 錯誤:簽名用的引數順序與 final_query 不一致,會報 -1022

規則 2:signature 不參與簽名本身

# signature 欄位永遠放在最後,不能參與 HMAC 計算
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
sig = hmac_sha256(urlencode(params))  # 先算簽名
params["signature"] = sig               # 再加進去

規則 3:數值不加引號、字串不帶空格

# 對
timestamp=1713027384562

# 錯(會得到不同的簽名)
timestamp="1713027384562"
timestamp= 1713027384562

三、Python 標準實現

import hmac
import hashlib
from urllib.parse import urlencode

SECRET_KEY = "YOUR_SECRET_KEY"

def sign(params: dict) -> str:
    """標準簽名實現"""
    query = urlencode(params)
    return hmac.new(
        SECRET_KEY.encode('utf-8'),
        query.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

# 測試
params = {
    "symbol": "BTCUSDT",
    "side": "BUY",
    "type": "LIMIT",
    "timeInForce": "GTC",
    "quantity": "0.001",
    "price": "60000.00",
    "timestamp": 1713027384562
}
print(sign(params))
# 輸出類似: b42e1fa3d8c7e9f2a6b5c4d1e8f9a0b3c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9

四、Node.js 標準實現

const crypto = require('crypto');

const SECRET_KEY = 'YOUR_SECRET_KEY';

function sign(params) {
  // URLSearchParams 自動處理編碼
  const query = new URLSearchParams(params).toString();
  return crypto
    .createHmac('sha256', SECRET_KEY)
    .update(query)
    .digest('hex');
}

const params = {
  symbol: 'BTCUSDT',
  side: 'BUY',
  type: 'LIMIT',
  timeInForce: 'GTC',
  quantity: '0.001',
  price: '60000.00',
  timestamp: Date.now()
};

console.log(sign(params));

注意:Node.js 中 JSON.stringify 得到的是 JSON 字串,不能用於簽名;必須用 URLSearchParams 或手工拼接。

五、Go 標準實現

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "net/url"
)

func sign(params url.Values, secret string) string {
    query := params.Encode()
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(query))
    return hex.EncodeToString(h.Sum(nil))
}

func main() {
    params := url.Values{}
    params.Set("symbol", "BTCUSDT")
    params.Set("side", "BUY")
    params.Set("type", "LIMIT")
    params.Set("timeInForce", "GTC")
    params.Set("quantity", "0.001")
    params.Set("price", "60000.00")
    params.Set("timestamp", "1713027384562")

    fmt.Println(sign(params, "YOUR_SECRET_KEY"))
}

關鍵url.Values.Encode()自動按字母排序並做 URL 編碼,與 Python 的 urlencode 輸出一致。

六、Rust 實現

use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

fn sign(query: &str, secret: &str) -> String {
    let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
        .expect("HMAC init error");
    mac.update(query.as_bytes());
    hex::encode(mac.finalize().into_bytes())
}

fn main() {
    let query = "symbol=BTCUSDT&side=BUY&type=LIMIT&timeInForce=GTC&quantity=0.001&price=60000.00&timestamp=1713027384562";
    let secret = "YOUR_SECRET_KEY";
    println!("{}", sign(query, secret));
}

Cargo.toml:

[dependencies]
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"

七、Java 實現

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class BinanceSigner {
    public static String sign(String query, String secret) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(
            secret.getBytes(StandardCharsets.UTF_8),
            "HmacSHA256"
        );
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(keySpec);
        byte[] hash = mac.doFinal(query.getBytes(StandardCharsets.UTF_8));

        StringBuilder hex = new StringBuilder();
        for (byte b : hash) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    public static void main(String[] args) throws Exception {
        String query = "symbol=BTCUSDT&timestamp=1713027384562";
        String secret = "YOUR_SECRET_KEY";
        System.out.println(sign(query, secret));
    }
}

八、Ed25519 替代方案(新)

2023 年幣安推出 Self-generated API Key 允許使用 Ed25519 非對稱簽名。優勢:Secret 永不離開本地

1. 生成金鑰對

openssl genpkey -algorithm Ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
# 上傳 public.pem 到幣安 API 管理頁

2. Python Ed25519 簽名

from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64

with open("private.pem", "rb") as f:
    private_key = load_pem_private_key(f.read(), password=None)

def ed25519_sign(query: str) -> str:
    signature_bytes = private_key.sign(query.encode())
    return base64.b64encode(signature_bytes).decode()

# 使用方式類似 HMAC,但簽名結果是 base64 而非 hex
query = "symbol=BTCUSDT&timestamp=1713027384562"
sig = ed25519_sign(query)
final_url = f"/api/v3/order?{query}&signature={sig}"

九、簽名錯誤排查清單

遇到 -1022 Signature for this request is not valid 時,按順序檢查:

# 問題 驗證方法
1 Secret Key 多餘空格/換行 列印 len(secret),應為 64 位
2 引數順序簽名與請求不一致 列印 signer 用的字串和實際 query,對比
3 signature 參與了 HMAC 檢查程式碼是否先算簽名再加欄位
4 URL 編碼方式不同 @ 有些庫編為 %40,確保簽名和請求同源
5 數值加了引號 timestamp="123"timestamp=123 產生不同簽名
6 字符集錯誤 中文字元必須 UTF-8 編碼
7 金鑰型別混用 HMAC 金鑰對應 HMAC 演算法,Ed25519 對應 Ed25519
8 POST body vs query string POST 時引數應放 body 或 URL,兩處都放會衝突

十、簽名除錯工具程式碼

def debug_sign(params, secret):
    """詳細列印每一步"""
    query = urlencode(params)
    print(f"Step 1. 查詢字串: {query}")
    print(f"        長度: {len(query)} 位元組")

    sig = hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
    print(f"Step 2. 簽名: {sig}")
    print(f"        長度: {len(sig)} 字元(應為 64)")

    final = f"{query}&signature={sig}"
    print(f"Step 3. 最終 URL: {final}")
    return sig

debug_sign({"symbol": "BTCUSDT", "timestamp": 1713027384562}, "YOUR_SECRET")

十一、常見問題 FAQ

Q1: 簽名結果為什麼每次都一樣(同樣引數)?

A: HMAC-SHA256 是確定性演算法,相同輸入總是得到相同輸出。這是為什麼時間戳必須變化——timestamp 每毫秒都不同,確保每次簽名不同,防止重放攻擊。

Q2: 簽名會被反推出 Secret Key 嗎?

A: 不會。SHA-256 是單向函式,從 HMAC 值反推 Secret 在數學上不可行(需要約 2^128 次計算)。Secret 洩露只可能是你自己洩漏了。

Q3: signature 為什麼是 64 位?

A: SHA-256 輸出 256 位 = 32 位元組,每位元組轉成兩個十六進位制字元,正好 64 位字元。如果你的簽名長度不是 64 位,說明實現有問題。

Q4: 時間戳用 microseconds 可以嗎?

A: 不可以。幣安要求毫秒(13 位數字)。傳 microseconds(16 位)會被識別為未來幾十年的時間,直接報 -1021 Timestamp out of recv window

Q5: Ed25519 和 HMAC-SHA256 哪個更安全?

A: Ed25519 更安全:私鑰永遠不出你的機器,幣安只拿到公鑰。即使幣安伺服器被攻破,你的資產也不會受影響。但 Ed25519 的 SDK 支援遠不如 HMAC 成熟,一般量化策略用 HMAC 已足夠(Secret 存本地加密檔案即可)。

看完簽名原理,回到 分類導航 檢視「API接入」分類其它技術專題。

繼續瀏覽

對幣安使用還有疑問?回到分類頁查詢同主題的其它教程。

分類導航

相關教程

幣安API怎麼申請?金鑰簽名一般要怎麼生成 2026-04-14 幣安Spot API怎麼用?從零到第一單的可執行程式碼 2026-04-14 幣安Futures和Spot API有什麼區別?端點引數權重對比 2026-04-14 幣安API會被封IP嗎?限頻策略與權重計算詳解 2026-04-14