Saltar al contenido
ecdsa.com
Go

Go crypto/ecdsa (standard library)

Los paquetes de firma de la biblioteca estándar de Go (crypto/ecdsa con crypto/x509, ed25519, mlkem y mldsa): firmas DER sobre un resumen que proporcionas tú, nonces aleatorios que ignoran tu Reader y solo curvas NIST.
Licencia
BSD-3-Clause
Versión comprobada
1.27.3 ·
Código fuente
github.com/golang/go
Documentación
pkg.go.dev/crypto/ecdsa

Compatibilidad de un vistazo

ECDSAEdDSACriptografía poscuántica

Curvas ECDSA
P-224P-256P-384P-521
EdDSA
Ed25519Ed25519phEd25519ctx
Formatos de clave pública
spki-derx509sec1-uncompressed
Formatos de clave privada
pkcs8-dersec1-derraw-scalar

Go incluye su criptografía en la biblioteca estándar, y desde Go 1.24 las primitivas básicas residen en el Go Cryptographic Module, creado para FIPS 140-3. crypto/ecdsa firma y verifica, crypto/x509 lee claves y certificados, y Go 1.27.3 añade crypto/mlkem (ML-KEM-768/1024) y crypto/mldsa (ML-DSA-44/65/87) junto a crypto/ed25519. No hay secp256k1: el código de Ethereum y Bitcoin usa los paquetes de go-ethereum o de decred.

Todo en crypto/ecdsa opera sobre un resumen que calculas tú: el paquete nunca calcula el hash. Algo peor que una firma errónea: una entrada larga se trunca en silencio al tamaño de la curva, y una firma sobre una entrada de 46 bytes se verificó para otra entrada con los mismos 32 primeros bytes. Calcula siempre el hash primero, como hace el ejemplo. SignASN1, VerifyASN1 y la interfaz crypto.Signer trabajan con DER ASN.1; WebCrypto, JWS y otras implementaciones de r‖s en bruto necesitan Sign/Verify con los enteros rellenados mediante FillBytes: Bytes() elimina los bytes cero iniciales, así que cada r o s que empieza por un byte cero sale un byte más corto.

Las firmas son aleatorias y, desde Go 1.26, el io.Reader que pasas se ignora salvo que se establezca GODEBUG=cryptocustomrand=1: dos firmas hechas con un Reader de solo ceros fueron distintas. Las pruebas que dependían de un Reader fijo para obtener firmas reproducibles dejaron de funcionar; el sustituto documentado es testing/cryptotest.SetGlobalRandom. s no se normaliza y VerifyASN1 acepta ambas mitades, así que, para los verificadores low-S estrictos, hay que sustituir s por n − s en las firmas.

El análisis de claves está pensado para DER. x509.ParsePKIXPublicKey falla con asn1: structure error: tags don't match si recibe texto PEM: llama antes a pem.Decode. ecdsa.ParseUncompressedPublicKey y ecdsa.ParseRawPrivateKey leen el punto SEC1 y el escalar sin envoltorio; los puntos comprimidos se rechazan, y no hay lector para PKCS#8 cifrado con frase de contraseña. Una firma mal formada hace que VerifyASN1 devuelva false, nunca un error.

Para comparar una firma de Go con otra pila, conviértela en el conversor DER ⇄ en bruto y compruébala en el verificador de firmas; los problemas de análisis de certificados se ven con más facilidad en el decodificador de certificados.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/go-crypto-ecdsa/run.sh ejecutó Go crypto/ecdsa (standard library) 1.27.3 en la imagen de contenedor golang:1.27.3-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    der

    SignASN1, VerifyASN1 y PrivateKey.Sign (la vía de crypto.Signer) usan DER ASN.1; Sign y Verify reciben r y s como *big.Int.

    Registrado por la comprobación
    fact ecdsa-signature-encoding = der
  • Nonce de ECDSA

    random

    Aleatorio: dos firmas del mismo resumen son distintas, como indica la documentación del paquete (la entropía se mezcla con la clave y el mensaje).

    Registrado por la comprobación
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures with an all-zero Reader are identical: false
  • Argumento de fuente aleatoria

    ignored

    Desde Go 1.26 se ignora el argumento io.Reader: dos firmas hechas con un Reader de solo ceros fueron distintas. GODEBUG=cryptocustomrand=1 restaura por ahora el comportamiento anterior.

    Registrado por la comprobación
    fact go-rand-reader-argument = ignored
  • Low-S al firmar

    either

    s no se normaliza: aparecieron valores high-S entre 64 firmas.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = either
  • High-S al verificar

    accepts-high-s

    VerifyASN1 aceptó la firma con s sustituido por n − s.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
  • De qué calcula el hash la llamada de firma

    takes-digest

    Todas las funciones reciben un resumen; nada en crypto/ecdsa calcula el hash del mensaje.

    Registrado por la comprobación
    fact ecdsa-prehash = takes-digest
  • Resumen más largo que el orden de la curva

    truncated

    Una entrada más larga que el orden de la curva se trunca: una firma sobre una entrada de 46 bytes se verificó para otra entrada que compartía sus primeros 32 bytes.

    Registrado por la comprobación
    fact ecdsa-long-input = truncated
    evidence ecdsa-long-input: a signature over a 46-byte input verified for a different input with the same first 32 bytes: true
  • Firma ECDSA mal formada

    returns-false

    VerifyASN1 devolvió false para una firma r‖s en bruto y para un blob de 3 bytes.

    Registrado por la comprobación
    fact ecdsa-malformed-signature = returns-false
  • Curvas ECDSA

    P-224P-256P-384P-521

    P-224, P-256, P-384 y P-521; crypto/elliptic no ofrece curvas secp256k1 ni Brainpool.

    Registrado por la comprobación
    fact ecdsa-curves = P-224, P-256, P-384, P-521
    evidence ecdsa-curves: crypto/elliptic exposes P224, P256, P384 and P521 only
  • EdDSA

    Ed25519Ed25519phEd25519ctx

    Ed25519, además de las variantes Ed25519ph y Ed25519ctx del RFC 8032, mediante ed25519.Options.

    Registrado por la comprobación
    fact eddsa = Ed25519, Ed25519ph, Ed25519ctx
  • Estructura de la clave privada Ed25519

    expanded-64

    ed25519.PrivateKey ocupa 64 bytes: la semilla seguida de la clave pública; NewKeyFromSeed la construye a partir de la semilla de 32 bytes.

    Registrado por la comprobación
    fact ed25519-private-key = expanded-64
  • Algoritmos poscuánticos

    ML-KEMML-DSA

    Con ML-KEM-768 y ML-KEM-1024 se encapsuló y desencapsuló, y con ML-DSA-44/65/87 se firmó y verificó, en Go 1.27.3.

    Registrado por la comprobación
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: crypto/mlkem offers ML-KEM-768 and ML-KEM-1024; crypto/mldsa offers ML-DSA-44, ML-DSA-65 and ML-DSA-87
  • Formatos de clave pública

    spki-derx509sec1-uncompressed

    SPKI DER mediante x509.ParsePKIXPublicKey, certificados, y puntos SEC1 en formato sin comprimir mediante ecdsa.ParseUncompressedPublicKey; PEM requiere pem.Decode primero y los puntos comprimidos se rechazan.

    Registrado por la comprobación
    fact public-key-formats = spki-der, x509, sec1-uncompressed
    evidence public-key-formats: ParsePKIXPublicKey on PEM text: asn1: structure error: tags don't match (16 vs {class:0 tag:13 length:45 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} publicKeyInfo @2
    evidence public-key-formats: ParseUncompressedPublicKey on a compressed point: ecdsa: invalid uncompressed public key
    evidence public-key-formats: ecdh.P256().NewPublicKey on a compressed point: crypto/ecdh: invalid public key
  • Formatos de clave privada

    pkcs8-dersec1-derraw-scalar

    PKCS#8 DER, SEC1 DER y el escalar en bruto mediante ecdsa.ParseRawPrivateKey; no hay analizador para PKCS#8 cifrado con frase de contraseña.

    Registrado por la comprobación
    fact private-key-formats = pkcs8-der, sec1-der, raw-scalar
    evidence private-key-formats: no parser for passphrase-encrypted PKCS#8 in the standard library

Uso

Calcula primero el hash, firma en DER para implementaciones al estilo de OpenSSL y construye un r‖s en bruto con el relleno correcto para WebCrypto y JOSE.
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
)

func main() {
	key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	// crypto/ecdsa never hashes: pass a digest, never the message itself.
	digest := sha256.Sum256([]byte("order #1042 approved"))

	der, _ := ecdsa.SignASN1(rand.Reader, key, digest[:]) // ASN.1 DER for OpenSSL, Java, .NET peers
	fmt.Println("DER verified:", ecdsa.VerifyASN1(&key.PublicKey, digest[:], der))

	// Raw r||s for WebCrypto and JOSE: FillBytes pads to 32 bytes; Bytes() would not.
	r, s, _ := ecdsa.Sign(rand.Reader, key, digest[:])
	raw := make([]byte, 64)
	r.FillBytes(raw[:32])
	s.FillBytes(raw[32:])
	ok := ecdsa.Verify(&key.PublicKey, digest[:], new(big.Int).SetBytes(raw[:32]), new(big.Int).SetBytes(raw[32:]))
	fmt.Println("raw verified:", ok, len(raw))
}

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor golang:1.27.3-trixie, y la ejecución falla si la salida no coincide.

Comando
go run .
La salida debe coincidir (expresión regular)
DER verified: true\nraw verified: true 64

Bibliotecas