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