币安 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接入」分类其它技术专题。