API Integration

How to Generate Binance API Signatures? HMAC-SHA256 Multi-Language Implementation

Complete guide to Binance API HMAC-SHA256 signatures: Algorithm principles, parameter serialization rules, URL encoding details, Ed25519 alternatives, multi-language implementations for Python, Node.js, Go, Rust, and Java, and troubleshooting common signature errors.

The standard algorithm for Binance API signatures is HMAC-SHA256. The principle involves using your Secret Key as the cryptographic key and the query string as the message to generate a 64-character hexadecimal string, which is then appended to the request as the signature parameter. Starting in 2023, Binance introduced the Ed25519 asymmetric signature scheme, which offers higher security but has lower adoption. This article explains the algorithm's principles, provides implementations in five programming languages, and outlines troubleshooting methods for eight common signature errors. If you haven't opened a Binance account yet, please complete registration on the Binance Official Website; those without an account can Register for Free.

I. Mathematical Principles of HMAC-SHA256

HMAC (Hash-based Message Authentication Code) is a message authentication code based on a hash function. The formula is:

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

Where:

  • K is the Secret Key.
  • m is the message (the query string).
  • H is the SHA256 hash function.
  • ipad = 0x36 repeated 64 times.
  • opad = 0x5C repeated 64 times.
  • denotes XOR, and || denotes concatenation.

The specific Binance workflow:

  1. Concatenate all parameters in order into the format key1=value1&key2=value2.
  2. Use the Secret Key as the HMAC key and the resulting string as the message.
  3. Calculate the SHA256 digest (32 bytes).
  4. Convert it into a 64-character hexadecimal string.

II. Three Rules for Binance API Parameters

Rule 1: Parameter Order Must Match the Signature

# Correct: The signature matches the request query
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
signature = hmac_sha256("symbol=BTCUSDT&timestamp=1713027384562")
final_query = "symbol=BTCUSDT&timestamp=1713027384562&signature=" + signature

# Wrong: If the parameter order used for signing differs from final_query, it results in -1022.

Rule 2: The signature Itself Does Not Participate in Signing

# The signature field is always placed last and cannot be included in the HMAC calculation.
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
sig = hmac_sha256(urlencode(params))  # Calculate signature first
params["signature"] = sig               # Add it afterwards

Rule 3: No Quotes for Numbers, No Spaces for Strings

# Correct
timestamp=1713027384562

# Wrong (produces a different signature)
timestamp="1713027384562"
timestamp= 1713027384562

III. Python Implementation

import hmac
import hashlib
from urllib.parse import urlencode

SECRET_KEY = "YOUR_SECRET_KEY"

def sign(params: dict) -> str:
    """Standard signature implementation"""
    query = urlencode(params)
    return hmac.new(
        SECRET_KEY.encode('utf-8'),
        query.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

# Testing
params = {
    "symbol": "BTCUSDT",
    "side": "BUY",
    "type": "LIMIT",
    "timeInForce": "GTC",
    "quantity": "0.001",
    "price": "60000.00",
    "timestamp": 1713027384562
}
print(sign(params))
# Output resembles: b42e1fa3d8c7e9f2a6b5c4d1e8f9a0b3c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9

IV. Node.js Implementation

const crypto = require('crypto');

const SECRET_KEY = 'YOUR_SECRET_KEY';

function sign(params) {
  // URLSearchParams handles encoding automatically
  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));

Note: In Node.js, JSON.stringify produces a JSON string which cannot be used for signing; you must use URLSearchParams or manual concatenation.

V. Go Implementation

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"))
}

Key Point: url.Values.Encode() automatically sorts alphabetically and applies URL encoding, matching the output of Python's urlencode.

VI. Rust Implementation

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"

VII. Java Implementation

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));
    }
}

VIII. Ed25519 Alternative (New)

In 2023, Binance launched Self-generated API Keys that allow the use of Ed25519 asymmetric signatures. Advantage: The Secret never leaves your local machine.

1. Generate Key Pair

openssl genpkey -algorithm Ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
# Upload public.pem to the Binance API Management page

2. Python Ed25519 Signing

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()

# Usage matches HMAC, but the signature result is base64 instead of hex
query = "symbol=BTCUSDT&timestamp=1713027384562"
sig = ed25519_sign(query)
final_url = f"/api/v3/order?{query}&signature={sig}"

IX. Signature Error Troubleshooting Checklist

When encountering -1022 Signature for this request is not valid, check these in order:

# Issue Verification Method
1 Extra spaces/newlines in Secret Key Print len(secret); it should be 64 characters.
2 Parameter order mismatch Print the string used for signing and the actual query; compare them.
3 signature included in HMAC Check if your code calculates the signature before adding the field.
4 Different URL encoding methods Some libraries encode @ as %40; ensure consistency.
5 Quotes added to numeric values timestamp="123" vs timestamp=123 produces different signatures.
6 Incorrect character set Chinese characters must be UTF-8 encoded.
7 Mixed key types HMAC keys match HMAC algorithms; Ed25519 matches Ed25519.
8 POST body vs query string Parameters should be in either the body or URL; using both causes conflict.

X. Signature Debugging Code

def debug_sign(params, secret):
    """Prints each step in detail"""
    query = urlencode(params)
    print(f"Step 1. Query String: {query}")
    print(f"        Length: {len(query)} bytes")

    sig = hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
    print(f"Step 2. Signature: {sig}")
    print(f"        Length: {len(sig)} chars (should be 64)")

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

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

XI. Common Questions FAQ

Q1: Why is the signature result always the same (for the same parameters)?

A: HMAC-SHA256 is a deterministic algorithm; identical input always yields identical output. This is why the timestamp must change—timestamp is unique every millisecond, ensuring every signature is different to prevent replay attacks.

Q2: Can the Secret Key be reverse-engineered from the signature?

A: No. SHA-256 is a one-way function. Mathematically, deriving the Secret from an HMAC value is infeasible (requiring roughly 2^128 operations). Secret leakage only happens if you expose it yourself.

Q3: Why is the signature 64 characters long?

A: SHA-256 outputs 256 bits = 32 bytes. Each byte is converted into two hexadecimal characters, resulting in exactly 64 characters. If your signature length is not 64, your implementation is incorrect.

Q4: Can I use microseconds for the timestamp?

A: No. Binance requires milliseconds (13 digits). Passing microseconds (16 digits) will be interpreted as a time decades into the future, triggering -1021 Timestamp out of recv window.

Q5: Which is more secure: Ed25519 or HMAC-SHA256?

A: Ed25519 is more secure: The private key never leaves your machine; Binance only receives the public key. Even if Binance servers were compromised, your assets would remain safe. However, SDK support for Ed25519 is less mature than for HMAC. For most quantitative strategies, HMAC is sufficient (provided the Secret is stored in an encrypted local file).

After reviewing signature principles, return to the [Category Navigation](/en/vault/API Integration/) to explore other technical topics under "API Integration".

Keep reading

Still have Binance questions? Head back to the category page for more tutorials on the same topic.

Categories

Related tutorials

How to Apply for Binance API? Common Guide for Key Generation and Signatures 2026-04-14 How to Use Binance Spot API? Executable Code from Zero to Your First Order 2026-04-14 What are the Differences Between Binance Futures and Spot APIs? Endpoint, Parameter, and Weight Comparison 2026-04-14 Will My IP Get Banned? Detailed Explanation of Binance API Rate Limits and Weights 2026-04-14