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:
- Concatenar todos los parámetros en orden bajo la forma
key1=value1&key2=value2 - Usar la Secret Key como clave HMAC y la cadena anterior como mensaje
- Calcular el resumen SHA256, obteniendo 32 bytes
- 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×tamp=1713027384562")
final_query = "symbol=BTCUSDT×tamp=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×tamp=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×tamp=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×tamp=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».