Integración API

¿Cómo generar la firma de la API de Binance? Implementación multi-lenguaje de HMAC-SHA256

Explicación completa de la firma HMAC-SHA256 de la API de Binance: principios del algoritmo, reglas de serialización de parámetros, detalles de codificación URL, alternativa Ed25519, implementación en Python/Node.js/Go/Rust/Java y resolución de errores comunes de firma.

El algoritmo estándar para las firmas de la API de Binance es HMAC-SHA256. El principio consiste en utilizar la Secret Key como clave y la cadena de consulta (query string) como mensaje para generar una cadena hexadecimal de 64 caracteres que se adjunta al final de la solicitud como el parámetro signature. Desde 2023, Binance ha añadido el esquema de firma asimétrica Ed25519, que ofrece mayor seguridad pero tiene una tasa de adopción menor. Este artículo detalla los principios del algoritmo, su implementación en 5 lenguajes y métodos para solucionar 8 errores comunes de firma. Si aún no tiene una cuenta de Binance, complete su registro en el sitio oficial de Binance; quienes no tengan cuenta pueden registrarse gratis aquí.

I. Principios matemáticos de HMAC-SHA256

HMAC (Hash-based Message Authentication Code) es un código de autenticación de mensajes basado en funciones hash, cuya fórmula es:

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

Donde:

  • K es la clave (Secret Key)
  • m es el mensaje (cadena de consulta)
  • H es la función hash SHA256
  • ipad = 0x36 repetido 64 veces
  • opad = 0x5C repetido 64 veces
  • ⊕ es XOR, || es concatenación

Proceso específico en Binance:

  1. Concatenar todos los parámetros en orden bajo la forma key1=value1&key2=value2
  2. Usar la Secret Key como clave HMAC y la cadena anterior como mensaje
  3. Calcular el resumen SHA256, obteniendo 32 bytes
  4. Convertirlo a una cadena hexadecimal de 64 caracteres

II. Las 3 reglas de los parámetros de firma en Binance

Regla 1: El orden de los parámetros debe coincidir con la firma

# Correcto: La firma coincide con la query de la solicitud
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
signature = hmac_sha256("symbol=BTCUSDT&timestamp=1713027384562")
final_query = "symbol=BTCUSDT&timestamp=1713027384562&signature=" + signature

# Error: Si el orden de los parámetros en la firma difiere de final_query, devolverá error -1022

Regla 2: 'signature' no participa en la firma misma

# El campo signature siempre se coloca al final y no debe incluirse en el cálculo HMAC
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
sig = hmac_sha256(urlencode(params))  # Primero se calcula la firma
params["signature"] = sig               # Luego se añade

Regla 3: Los valores numéricos no llevan comillas; las cadenas no llevan espacios

# Correcto
timestamp=1713027384562

# Incorrecto (generará una firma distinta)
timestamp="1713027384562"
timestamp= 1713027384562

III. Implementación estándar en Python

import hmac
import hashlib
from urllib.parse import urlencode

SECRET_KEY = "TU_SECRET_KEY"

def sign(params: dict) -> str:
    """Implementación estándar de firma"""
    query = urlencode(params)
    return hmac.new(
        SECRET_KEY.encode('utf-8'),
        query.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

# Prueba
params = {
    "symbol": "BTCUSDT",
    "side": "BUY",
    "type": "LIMIT",
    "timeInForce": "GTC",
    "quantity": "0.001",
    "price": "60000.00",
    "timestamp": 1713027384562
}
print(sign(params))
# Salida similar a: b42e1fa3d8c7e9f2a6b5c4d1e8f9a0b3c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9

IV. Implementación estándar en Node.js

const crypto = require('crypto');

const SECRET_KEY = 'TU_SECRET_KEY';

function sign(params) {
  // URLSearchParams maneja automáticamente la codificación
  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));

Nota: En Node.js, JSON.stringify genera una cadena JSON que no sirve para la firma; debe usar URLSearchParams o concatenar manualmente.

V. Implementación estándar en 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, "TU_SECRET_KEY"))
}

Clave: url.Values.Encode() ordena alfabéticamente y codifica URL automáticamente, coincidiendo con la salida de urlencode en Python.

VI. Implementación en 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("Error al iniciar HMAC");
    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 = "TU_SECRET_KEY";
    println!("{}", sign(query, secret));
}

Cargo.toml:

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

VII. Implementación en 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 = "TU_SECRET_KEY";
        System.out.println(sign(query, secret));
    }
}

VIII. Alternativa Ed25519 (Nueva)

En 2023, Binance lanzó las Self-generated API Key, permitiendo el uso de firmas asimétricas Ed25519. Ventaja: La Secret Key nunca sale del entorno local.

1. Generar par de claves

openssl genpkey -algorithm Ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
# Subir public.pem a la página de gestión de API de Binance

2. Firma Ed25519 en Python

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

# El uso es similar a HMAC, pero el resultado es base64 en lugar de hex
query = "symbol=BTCUSDT&timestamp=1713027384562"
sig = ed25519_sign(query)
final_url = f"/api/v3/order?{query}&signature={sig}"

IX. Lista de verificación para errores de firma

Si encuentra el error -1022 Signature for this request is not valid, verifique en orden:

# Problema Método de verificación
1 Espacios o saltos de línea extra en Secret Key Imprimir len(secret), debe ser de 64 caracteres
2 Orden de parámetros distinto en firma y solicitud Imprimir la cadena del firmante y la query real para comparar
3 'signature' se incluyó en el HMAC Revisar si el código calcula la firma antes de añadir el campo
4 Diferencia en codificación URL Algunas bibliotecas codifican @ como %40; asegurar consistencia entre firma y solicitud
5 Valores numéricos con comillas timestamp="123" y timestamp=123 producen firmas distintas
6 Error de juego de caracteres Caracteres no ASCII deben codificarse en UTF-8
7 Mezcla de tipos de clave Clave HMAC para algoritmo HMAC, Ed25519 para Ed25519
8 POST body vs query string En POST, los parámetros deben estar en el body o la URL; ponerlos en ambos causa conflictos

X. Código de herramienta para depuración de firmas

def debug_sign(params, secret):
    """Imprime detalles de cada paso"""
    query = urlencode(params)
    print(f"Paso 1. Cadena de consulta: {query}")
    print(f"        Longitud: {len(query)} bytes")

    sig = hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
    print(f"Paso 2. Firma: {sig}")
    print(f"        Longitud: {len(sig)} caracteres (debe ser 64)")

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

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

XI. Preguntas frecuentes (FAQ)

P1: ¿Por qué el resultado de la firma es siempre el mismo para los mismos parámetros?

R: HMAC-SHA256 es un algoritmo determinista; la misma entrada siempre produce la misma salida. Por ello es vital que el timestamp varíe: al ser distinto cada milisegundo, asegura que cada firma sea única y evita ataques de repetición.

P2: ¿Se puede deducir la Secret Key a partir de la firma?

R: No. SHA-256 es una función unidireccional; deducir el Secret a partir de un valor HMAC es matemáticamente inviable (requiere aproximadamente 2^128 cálculos). La filtración del Secret solo ocurre si usted lo expone.

P3: ¿Por qué la firma tiene 64 caracteres?

R: SHA-256 produce 256 bits = 32 bytes. Al convertir cada byte en dos caracteres hexadecimales, se obtienen exactamente 64 caracteres. Si su firma tiene otra longitud, hay un error en la implementación.

P4: ¿Se pueden usar microsegundos en el timestamp?

R: No. Binance requiere milisegundos (13 dígitos). El uso de microsegundos (16 dígitos) se interpretará como una fecha dentro de varias décadas, devolviendo el error -1021 Timestamp out of recv window.

P5: ¿Cuál es más seguro, Ed25519 o HMAC-SHA256?

R: Ed25519 es más seguro: la clave privada nunca sale de su máquina y Binance solo posee la clave pública. Incluso si los servidores de Binance se vieran comprometidos, sus activos no se verían afectados. Sin embargo, el soporte de SDK para Ed25519 es menos maduro que para HMAC; para la mayoría de las estrategias cuantitativas, HMAC es suficiente (siempre que guarde el Secret en un archivo local cifrado).

Tras revisar los principios de la firma, regrese a la Navegación de categorías para consultar otros temas técnicos en la sección «Integración API».

Continuar explorando

¿Sigues con dudas sobre el uso de Binance? Vuelve a la página de categorías para encontrar otros tutoriales sobre el mismo tema.

Categorías

Tutoriales relacionados

¿Cómo solicitar la API de Binance? Guía para generar claves y firmas 2026-04-14 ¿Cómo usar la API Spot de Binance? Código listo para ejecutar desde cero hasta tu primera orden 2026-04-14 ¿Cuál es la diferencia entre la API de Binance Futures y Spot? Comparativa de endpoints, parámetros y pesos 2026-04-14 ¿Banearán mi IP de la API de Binance? Estrategias de límite de frecuencia y pesos explicadas 2026-04-14