Pular para o conteúdo
ecdsa.com
Go

Go crypto/ecdsa (standard library)

Os pacotes de assinatura da biblioteca padrão do Go — crypto/ecdsa com crypto/x509, ed25519, mlkem e mldsa: assinaturas DER sobre um digest fornecido por você, nonces aleatórios que ignoram seu Reader e apenas curvas do NIST.
Licença
BSD-3-Clause
Versão verificada
1.27.3 ·
Código-fonte
github.com/golang/go
Documentação
pkg.go.dev/crypto/ecdsa

Visão geral da compatibilidade

ECDSAEdDSAPós-quântica

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

O Go traz sua criptografia na biblioteca padrão, e desde o Go 1.24 as primitivas centrais ficam no Go Cryptographic Module, construído para o FIPS 140-3. crypto/ecdsa assina e verifica, crypto/x509 lê chaves e certificados, e o Go 1.27.3 acrescenta crypto/mlkem (ML-KEM-768/1024) e crypto/mldsa (ML-DSA-44/65/87) ao lado de crypto/ed25519. Não há secp256k1: código de Ethereum e Bitcoin usa os pacotes do go-ethereum ou do decred.

Tudo em crypto/ecdsa opera sobre um digest calculado por você — o pacote nunca calcula hashes. Pior do que uma assinatura errada: uma entrada longa é truncada silenciosamente para o tamanho da curva, e uma assinatura sobre uma entrada de 46 bytes foi verificada para outra entrada com os mesmos 32 primeiros bytes. Calcule sempre o hash antes, como faz o exemplo. SignASN1, VerifyASN1 e a interface crypto.Signer falam ASN.1 DER; WebCrypto, JWS e outras contrapartes que usam r‖s bruto precisam de Sign/Verify com os inteiros preenchidos por FillBytes — Bytes() descarta bytes zero à esquerda, então todo r ou s que por acaso comece com um byte zero sai com um byte a menos.

As assinaturas são aleatorizadas e, desde o Go 1.26, o io.Reader que você passa é ignorado, a menos que GODEBUG=cryptocustomrand=1 esteja definido: duas assinaturas feitas com um Reader só de zeros foram diferentes. Testes que dependiam de um Reader fixo para obter assinaturas reproduzíveis pararam de funcionar; a substituição documentada é testing/cryptotest.SetGlobalRandom. O s não é normalizado e VerifyASN1 aceita as duas metades, então assinaturas destinadas a verificadores low-S estritos precisam ter o s substituído por n − s.

A leitura de chaves parte do DER. x509.ParsePKIXPublicKey falha com asn1: structure error: tags don't match quando recebe texto PEM — chame pem.Decode antes. ecdsa.ParseUncompressedPublicKey e ecdsa.ParseRawPrivateKey leem o ponto SEC1 e o escalar puros; pontos comprimidos são recusados, e não há leitor para PKCS#8 criptografado com senha. Uma assinatura malformada faz VerifyASN1 retornar false, nunca um erro.

Para comparar uma assinatura do Go com a de outra pilha, converta-a no conversor DER ⇄ bruto e confira-a no verificador de assinaturas; problemas na leitura de certificados ficam mais fáceis de ver no decodificador de certificados.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/go-crypto-ecdsa/run.sh executou Go crypto/ecdsa (standard library) 1.27.3 na imagem de contêiner golang:1.27.3-trixie em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Codificação da assinatura ECDSA

    der

    SignASN1, VerifyASN1 e PrivateKey.Sign (o caminho de crypto.Signer) usam ASN.1 DER; Sign e Verify recebem r e s como *big.Int.

    Registrado pela verificação
    fact ecdsa-signature-encoding = der
  • Nonce do ECDSA

    random

    Aleatório: duas assinaturas do mesmo digest são diferentes, como afirma a documentação do pacote (a entropia é misturada com a chave e a mensagem).

    Registrado pela verificação
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures with an all-zero Reader are identical: false
  • Argumento da fonte de aleatoriedade

    ignored

    Desde o Go 1.26, o argumento io.Reader é ignorado: duas assinaturas feitas com um Reader só de zeros foram diferentes. GODEBUG=cryptocustomrand=1 restaura, por enquanto, o comportamento antigo.

    Registrado pela verificação
    fact go-rand-reader-argument = ignored
  • Low-S ao assinar

    either

    O s não é normalizado: valores high-S apareceram entre 64 assinaturas.

    Registrado pela verificação
    fact ecdsa-low-s-sign = either
  • High-S ao verificar

    accepts-high-s

    VerifyASN1 aceitou a assinatura com s substituído por n − s.

    Registrado pela verificação
    fact ecdsa-low-s-verify = accepts-high-s
  • Entrada do hash na chamada de assinatura

    takes-digest

    Todas as funções recebem um digest; nada em crypto/ecdsa calcula o hash da mensagem.

    Registrado pela verificação
    fact ecdsa-prehash = takes-digest
  • Digest mais longo que a ordem da curva

    truncated

    Uma entrada maior que a ordem da curva é truncada: uma assinatura sobre uma entrada de 46 bytes foi verificada para outra entrada com os mesmos 32 primeiros bytes.

    Registrado pela verificação
    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
  • Assinatura ECDSA malformada

    returns-false

    VerifyASN1 retornou false para uma assinatura r‖s bruta e para um blob de 3 bytes.

    Registrado pela verificação
    fact ecdsa-malformed-signature = returns-false
  • Curvas ECDSA

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

    P-224, P-256, P-384 e P-521; crypto/elliptic não oferece secp256k1 nem curvas Brainpool.

    Registrado pela verificação
    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, mais as variantes Ed25519ph e Ed25519ctx da RFC 8032 por meio de ed25519.Options.

    Registrado pela verificação
    fact eddsa = Ed25519, Ed25519ph, Ed25519ctx
  • Estrutura da chave privada Ed25519

    expanded-64

    ed25519.PrivateKey tem 64 bytes: a semente seguida da chave pública; NewKeyFromSeed a constrói a partir da semente de 32 bytes.

    Registrado pela verificação
    fact ed25519-private-key = expanded-64
  • Algoritmos pós-quânticos

    ML-KEMML-DSA

    No Go 1.27.3, o encapsulamento e o desencapsulamento funcionaram com ML-KEM-768 e ML-KEM-1024, e a assinatura e a verificação, com ML-DSA-44/65/87.

    Registrado pela verificação
    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 chave pública

    spki-derx509sec1-uncompressed

    SPKI DER via x509.ParsePKIXPublicKey, certificados e pontos SEC1 na forma não comprimida via ecdsa.ParseUncompressedPublicKey; PEM exige pem.Decode antes, e pontos comprimidos são recusados.

    Registrado pela verificação
    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 chave privada

    pkcs8-dersec1-derraw-scalar

    PKCS#8 DER, SEC1 DER e o escalar bruto via ecdsa.ParseRawPrivateKey; não há parser para PKCS#8 criptografado com senha.

    Registrado pela verificação
    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

Calcule o hash antes, assine em DER para contrapartes no estilo do OpenSSL e monte um r‖s bruto com o padding correto para WebCrypto e 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))
}

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner golang:1.27.3-trixie, e a execução falha se a saída não corresponder.

Comando
go run .
A saída deve corresponder a (expressão regular)
DER verified: true\nraw verified: true 64

Bibliotecas