Saltar al contenido
ecdsa.com
← Todos los artículos

Reutilización del nonce en ECDSA: cómo un número repetido revela una clave privada

Cada firma ECDSA consume un secreto de un solo uso. Úsalo dos veces y las dos firmas, ambas públicas, contienen información suficiente para despejar la clave privada con álgebra escolar. No es una debilidad del algoritmo, sino una propiedad suya, y la cadena de bloques todavía conserva las pruebas.

El número que nunca debe repetirse

Una firma ECDSA es un par de enteros, (r, s). Para generarla hacen falta tres datos de entrada: la clave privada d, el hash del mensaje z y un entero secreto nuevo k, el nonce (abreviatura de «number used once», es decir, número de un solo uso). El firmante elige k en el rango [1, n−1], donde n es el orden del punto base G de la curva, y calcula:

Firma ECDSA: toda la aritmética es mod n
R = k * G                      (a point on the curve)
r = x-coordinate of R  mod n
s = k^-1 * (z + r * d) mod n

signature = (r, s)

Observa de dónde sale cada valor. s lo mezcla todo: el nonce, el hash y la clave. Pero r se deriva de k y de nada más: es la coordenada x de kG, una función que depende solo del nonce y de la curva. El mensaje no interviene. La clave privada no interviene.

Ese es todo el problema en una frase. Dos firmas generadas con el mismo k llevarán el mismo r, y r se publica con cada firma. Un nonce repetido no es un fallo oculto que haya que descubrir: se delata a la vista de todos, en un campo que lee cualquier verificador y que cualquier explorador de bloques guarda para siempre.

Dos firmas, dos incógnitas

Supón que un firmante usa el mismo k y la misma clave d para dos mensajes distintos, con hashes z1 y z2. Ambas firmas comparten el mismo r, así que tenemos:

Paso 1: las dos ecuaciones de firma
s1 = k^-1 * (z1 + r * d) mod n
s2 = k^-1 * (z2 + r * d) mod n

Aquí todo es público excepto k y d. Dos ecuaciones, dos incógnitas. Multiplica ambos miembros de cada una por k para eliminar el inverso:

Paso 2: eliminar el inverso modular
k * s1 = z1 + r * d mod n
k * s2 = z2 + r * d mod n

El término r * d es idéntico en las dos líneas, porque la misma clave firmó ambos mensajes y el mismo nonce produjo el mismo r. Resta la segunda ecuación de la primera y ese término se anula, con lo que la clave privada queda por completo fuera del cálculo:

Paso 3: se resta y d desaparece
k * s1 - k * s2 = z1 - z2      mod n
k * (s1 - s2)   = z1 - z2      mod n

k = (z1 - z2) * (s1 - s2)^-1   mod n

Así se recupera el nonce. Si lo sustituyes en cualquiera de las ecuaciones del paso 2, obtienes la clave, porque ahora r es lo único que multiplica a d:

Paso 4: despejar la clave privada
k * s1     = z1 + r * d        mod n
r * d      = k * s1 - z1       mod n

d = (s1 * k - z1) * r^-1       mod n

Cuatro pasos, sin búsqueda y sin adivinar nada. Todo se reduce a dos inversiones modulares y un puñado de multiplicaciones: microsegundos en cualquier equipo, con datos que ya son públicos.

Un matiz práctico: las codificaciones no siempre conservan exactamente el s que calculó el firmante. Bitcoin y varias bibliotecas normalizan a la forma low-S y sustituyen s por n − s cuando cae en la mitad superior del rango. Es una corrección de la maleabilidad propuesta en BIP-62 y especificada como regla de retransmisión en BIP-146; no tiene que ver con los nonces, pero se cruza con esta aritmética. El valor recuperado puede corresponder entonces a −k en lugar de a k, y hay que resolver el signo antes de que los números cuadren. Eso no cambia en nada la dificultad; la codificación se trata aparte en Formatos de firma ECDSA.

No es un experimento mental

Es la clase de fallo de ECDSA más productiva en el mundo real, y ninguno de sus casos se debió a las matemáticas. En todos ellos, una implementación suministra un nonce que no debería haber suministrado.

  • PlayStation 3, 2010. La implementación con la que Sony firmaba su firmware usaba un valor constante donde debía ir k. Todas las firmas que producía llevaban el mismo r, y la clave de firma maestra de la consola se podía obtener a partir de dos cualesquiera de ellas. El fallo se demostró públicamente en el 27C3, en la charla Console Hacking 2010. Es el ejemplo más puro que existe: el algoritmo estaba bien implementado salvo por una variable que nunca variaba.
  • Billeteras de Android, 2013. El 14 de agosto de 2013, el Android Developers Blog publicó Some SecureRandom Thoughts tras investigar lo que la entrada describe como «el compromiso de una transacción de bitcoin que llevó a actualizar varias aplicaciones de Bitcoin el 11 de agosto». La causa de fondo era que las aplicaciones que usan la Java Cryptography Architecture «podrían no recibir valores criptográficamente fuertes en dispositivos Android debido a una inicialización incorrecta del PRNG subyacente». Las billeteras que firmaban en los dispositivos afectados sacaban los nonces de un generador que podía arrancar una y otra vez en el mismo estado. Google publicó una solución provisional, PRNGFixes, para los niveles de API del 16 al 18 y corrigió la plataforma. Fíjate en qué falló: no el código criptográfico de la billetera, ni la curva, sino la primitiva del sistema operativo que hay debajo de ambos.
  • La clase más amplia: nonces sesgados. La repetición completa es la versión ruidosa. La versión silenciosa es un nonce que solo está sesgado: unos pocos bits que no se distribuyen de manera uniforme, porque el generador devuelve valores en un rango ligeramente incorrecto, trunca o filtra información de tiempos. Ahí no hay un atajo algebraico limpio, pero el problema puede reformularse como la búsqueda de un vector corto en un retículo, y la reducción de retículos lo resuelve. Biased Nonce Sense, de Breitner y Heninger (Financial Cryptography 2019), lo aplicó a cadenas de bloques públicas y a escaneos de todo internet, y recuperó cientos de claves de Bitcoin, además de decenas de claves de Ethereum, Ripple, SSH y HTTPS, a partir de nonces que nunca se repitieron: solo eran imperfectos. La lección es dura: «casi aleatorio» no es una defensa.

Ninguno de estos casos necesitó un defecto de ECDSA, y ninguno se arregla con una curva más grande. El algoritmo exige un secreto perfectamente uniforme para cada firma y no da ninguna advertencia cuando no lo recibe.

Lo que muestra hoy la cadena de bloques

Los valores de r repetidos siguen en la cadena de bloques de Bitcoin, para siempre, porque las cadenas de bloques no olvidan. Nuestro comprobador de reutilización de nonces obtiene las transacciones recientes de una dirección desde la API de mempool.space, extrae las firmas DER de las entradas que gastan desde esa dirección y las agrupa por (clave pública, r). Esto es lo que devolvió para 1BFhrfTTZP3Nw4BNy4eX4KFLsn9ZeijcMm el 20 de agosto de 2026:

POST /api/nonce-scan: 1BFhrfTTZP3Nw4BNy4eX4KFLsn9ZeijcMm
address                 1BFhrfTTZP3Nw4BNy4eX4KFLsn9ZeijcMm  (P2PKH)
transactions returned   50      <- API cap, not the full history
  spending from it      30
inputs from address     110
signatures parsed       110     (unattributed 0, taproot 0, unparsed 0)
distinct public keys    1
distinct r values       73      <- 110 signatures, only 73 nonces
repeated-r groups       6
identical duplicates    0

Una clave, 110 firmas, 73 nonces distintos. La diferencia lo dice todo: 43 de las 110 firmas están en un grupo que comparte r con al menos otra firma, y en cada uno de los seis grupos los valores de s difieren, lo que descarta la explicación inocente de una firma duplicada. El grupo más grande:

El mayor grupo con r repetido: 10 firmas, 10 valores de s distintos
r = d47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
pubkey = 04dbd0c6...49c5ff (uncompressed)

ad698b53bbba9e7d807559ec7d3a466fccb669dd3659a1604a206eeb9965c527  vin 0  2012-12-21
b4922bc8cc3624487cdcf4b0ed5a98a21a4552a48dfe495490a7443dd546821e  vin 0  2012-12-21
8ab813e5a4039eb79484c315e9c0e201c9d80385c3f3650a4c4b974db9172148  vin 0  2012-12-21
0b5315f4d472a22a6937a20a8d44b8e77099f2a951b65c53c9ebf912352504fa  vin 0  2012-12-19
952d8301780ca74fca5f6541db44ba49e47e8514dfdef36bc7e3d534085dbbf6  vin 0  2012-12-19
    ... 5 more, the oldest on 2012-08-16

Hay dos detalles que merecen atención. Primero, el segundo grupo más grande son otras diez firmas que comparten un r distinto, y en ambos grupos cada firma ocupa el mismo índice de entrada en su transacción (el índice 0 en uno y el 1 en el otro). Una colisión ocasional del RNG no tiene ese aspecto. Ese es el aspecto de un generador que se reinicializa al mismo estado al comienzo de cada operación de firma y luego avanza: la entrada 0 siempre recibe el primer valor; la entrada 1, el segundo. Segundo, la reutilización va de agosto a diciembre de 2012, casi un año antes de la divulgación del problema de SecureRandom en Android. Esta clase de defecto ya se daba en el mundo real antes de tener un nombre público, lo que recuerda que estos anuncios marcan cuándo se detectó un problema, no cuándo empezó.

La solución: dejar de tirar los dados

La solución duradera no es «usa un generador de números aleatorios mejor». Es sacar por completo el generador de números aleatorios del proceso de firma.

RFC 6979, ECDSA determinista. En lugar de muestrear k, derívalo: usa la clave privada y el hash del mensaje como semilla de un generador determinista de bits aleatorios basado en HMAC (HMAC-DRBG) y toma k de su salida; si el valor cae fuera de [1, n−1], genera otro. El resultado parece uniforme, es impredecible sin la clave y, lo que es crucial, es una función del mensaje. Un mensaje distinto produce necesariamente un k distinto, así que la reutilización accidental pasa a ser estructuralmente imposible, no solo improbable. Firmar dos veces el mismo mensaje reproduce los mismos bytes, lo cual es inofensivo y además resulta útil para las pruebas. En el momento de firmar no se consulta ninguna fuente de entropía, así que una fuente averiada no puede perjudicarte. FIPS 186-5 permite ECDSA determinista junto a la forma aleatorizada.

RFC 8032, EdDSA. Ed25519 y Ed448 hacen obligatoria la misma idea al integrarla en el esquema: el secreto de cada firma se deriva calculando el hash de un prefijo derivado de la clave junto con el mensaje, y la API no ofrece ningún punto a través del cual un implementador pueda poner un valor aleatorio en su lugar. Si vas a elegir un esquema en lugar de heredarlo, esto elimina el modo de fallo por construcción. Las firmas Taproot de Bitcoin siguen la misma filosofía: BIP-340 exige una derivación determinista del nonce con aleatoriedad auxiliar opcional.

Firmas hedged. El determinismo puro tiene una contrapartida que conviene conocer: un fallo provocado durante la firma —un glitch en el reloj o en el voltaje de una tarjeta inteligente— puede hacer que un firmante determinista produzca dos firmas distintas del mismo mensaje con el mismo k derivado, y esa es de nuevo la condición del nonce reutilizado. RFC 6979 lo prevé en el §3.6. La respuesta hedged mezcla aleatoriedad nueva en la derivación determinista en lugar de sustituirla, y así el nonce es seguro si cualquiera de los dos componentes —la derivación o el RNG— es sólido. El paquete crypto/ecdsa de Go firma así; @noble/curves lo ofrece como la opción extraEntropy; la aleatoriedad auxiliar de BIP-340 es la misma idea.

Así se comportan, de forma predeterminada, las bibliotecas más habituales:

Biblioteca¿Nonce determinista de forma predeterminada?Detalle
libsecp256k1 (Bitcoin Core)Sí: RFC 6979Si lo necesitas, se puede pasar otra función de nonce.
@noble/curvesSí: RFC 6979Modo hedged disponible mediante extraEntropy.
Rust p256 / k256 (RustCrypto)Sí: RFC 6979La misma clave y el mismo mensaje producen siempre los mismos bytes; la variante aleatorizada hay que activarla explícitamente.
Go crypto/ecdsaHedged, no RFC 6979Deriva el nonce de la clave, el mensaje y aleatoriedad nueva a la vez, así que los bytes cambian en cada ejecución y no se pueden fijar en las pruebas.
Python ecdsa (Python puro)OpcionalLa firma determinista es un método aparte; la llamada sign() normal usa aleatoriedad.
Python cryptography (pyca)OpcionalLas versiones recientes aceptan un indicador de firma determinista, y esa vía necesita un OpenSSL reciente por debajo.
CLI de OpenSSLOpcional (3.2+)El parámetro de firma nonce-type:1 con el algoritmo de resumen indicado explícitamente: pkeyutl -sign -pkeyopt nonce-type:1 -pkeyopt digest:sha256 o dgst -sha256 -sign key.pem -sigopt nonce-type:1.
Node.js node:cryptoNo: nonce aleatorioLa misma clave y el mismo mensaje producen bytes de firma distintos en cada llamada.
WebCrypto (crypto.subtle)No: nonce aleatorioLa API tampoco te permite proporcionar tu propio nonce.
Java JCA (SunEC)No: nonce aleatorioPara tener ECDSA determinista hay que añadir un proveedor como Bouncy Castle.
PHP openssl_* / Ruby OpenSSLNo: nonce aleatorioNinguno de los dos ofrece un modo determinista.

Un «No» en esa tabla no es un informe de error. Un nonce aleatorio obtenido de un CSPRNG sano del sistema operativo es una forma perfectamente conforme de firmar, y es lo que especifican todas las ediciones de FIPS 186; FIPS 186-5 (2023) añadió la variante determinista junto a la aleatoria. La columna mide dependencia, no corrección: las filas «No» se juegan tu clave privada a la fuente de entropía en el momento de firmar, y las filas «Sí», no. Que eso importe o no depende de tu despliegue: un servidor reforzado es una apuesta muy distinta de un dispositivo embebido recién arrancado, una imagen de máquina virtual clonada o un teléfono de 2013. La guía rápida de interoperabilidad entre bibliotecas recoge esta y las demás diferencias de comportamiento predeterminado, una al lado de otra.

Qué hacer en la práctica

  • Nunca proporciones tu propio k. Si una API de firma acepta un parámetro de nonce, trátalo como una forma de pegarte un tiro en el pie y no como una funcionalidad, y nunca recurras a él solo porque una prueba necesitaba bytes reproducibles. La firma determinista ya te los da, y de forma segura.
  • Nunca escribas el generador tú mismo. «Le damos como semilla la marca de tiempo y el ID de la solicitud» es exactamente como empiezan estos incidentes. Usa el CSPRNG de la plataforma o una biblioteca que no lo necesite.
  • Comprueba qué estrategia de nonce usas en realidad. Una prueba de dos líneas te dice la mayor parte, pero lee el resultado con cuidado, porque no distingue un nonce aleatorio de uno hedged:
node: ¿tu firmante es determinista?
import { generateKeyPairSync, sign } from "node:crypto";

const { privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
const msg = Buffer.from("same message, twice");

const a = sign("sha256", msg, privateKey);
const b = sign("sha256", msg, privateKey);

console.log(a.equals(b));
// false in node:crypto  -> random or hedged nonce
// true                  -> deterministic derivation (RFC 6979 or EdDSA-style)

Si los bytes difieren, todavía no sabes si el nonce es puramente aleatorio o hedged: eso solo lo aclara la documentación de la biblioteca. Si coinciden, confirma que se trata de RFC 6979 y no de algo casero, comparando tu salida con los vectores de respuesta conocida del apéndice A.2 de RFC 6979. Nuestro generador de vectores de prueba ECDSA produce firmas RFC 6979 para P-256, P-384, P-521 y secp256k1 a partir de una semilla, así que los mismos vectores se regeneran más adelante byte a byte.

  • Desconfía de los momentos de baja entropía. El primer arranque en hardware embebido, las máquinas virtuales y los contenedores restaurados desde una instantánea compartida y la inicialización temprana, antes de que la reserva de entropía reciba su semilla, son los momentos en que es más probable que un nonce aleatorio falle. La firma determinista hace innecesaria la pregunta.
  • Si encuentras reutilización, la clave está perdida. No debilitada: perdida, con efecto retroactivo, para cualquiera que haya visto alguna vez esas dos firmas. Mueve los fondos, rota la clave en todos los lugares donde se use y trata todas las firmas que haya producido como atribuibles a otra persona.

Fuentes primarias

  • RFC 6979: uso determinista de DSA y ECDSA. La derivación HMAC-DRBG, su justificación y los vectores de respuesta conocida del apéndice A; la advertencia sobre los ataques por inyección de fallos está en el §3.6.
  • RFC 8032: EdDSA (Ed25519, Ed448), donde la derivación determinista del nonce forma parte del esquema y no es una opción.
  • FIPS 186-5: la versión vigente del Digital Signature Standard (estándar de firma digital), que especifica ECDSA y permite generar el nonce de forma determinista.
  • Some SecureRandom Thoughts: Android Developers Blog, 14 de agosto de 2013. El defecto de inicialización del PRNG de la plataforma, su relación con el compromiso de las billeteras de Bitcoin y la solución provisional PRNGFixes.
  • Biased Nonce Sense: Breitner y Heninger, IACR ePrint 2019/023 / Financial Cryptography 2019. Ataques basados en retículos contra nonces que solo están sesgados, medidos con datos reales de cadenas de bloques y de todo internet.
  • Console Hacking 2010: 27C3, la demostración pública del fallo del nonce constante de PlayStation 3.
  • BIP-62 y BIP-146: canonicalización low-S (una corrección de la maleabilidad, no de los nonces, pero el motivo por el que los valores recuperados pueden necesitar una corrección de signo), además de BIP-340 para el nonce determinista obligatorio de Taproot.
  • libsecp256k1 y @noble/curves: dos implementaciones ampliamente auditadas que usan RFC 6979 de forma predeterminada y cuyo código fuente es la documentación más clara de cómo debe ser la derivación del nonce.

Por qué este requisito del nonce es una de las pocas ventajas operativas reales que aún conserva RSA, y cómo se comparan ambos algoritmos en lo demás, se explica en ECDSA frente a RSA.