幣安 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 次
- ⊕ 是異或,|| 是拼接
幣安具體流程:
- 把所有引數按順序拼成
key1=value1&key2=value2形式 - 用 Secret Key 作為 HMAC 金鑰、上述字串作為訊息
- 計算 SHA256 摘要,輸出 32 位元組
- 轉換為 64 位十六進位制字串
二、幣安簽名的 3 條引數規則
規則 1:引數順序必須與簽名一致
# 正確:簽名與請求 query 一致
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
signature = hmac_sha256("symbol=BTCUSDT×tamp=1713027384562")
final_query = "symbol=BTCUSDT×tamp=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×tamp=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×tamp=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×tamp=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接入」分類其它技術專題。