Zum Inhalt springen
ecdsa.com
Go

Go crypto/ecdsa (standard library)

Die Signaturpakete der Go-Standardbibliothek – crypto/ecdsa mit crypto/x509, ed25519, mlkem und mldsa: DER-Signaturen über einen Digest, den du selbst lieferst, zufällige Nonces, die deinen Reader ignorieren, nur NIST-Kurven.
Lizenz
BSD-3-Clause
Geprüfte Version
1.27.3 ·
Quellcode
github.com/golang/go
Dokumentation
pkg.go.dev/crypto/ecdsa

Kompatibilität auf einen Blick

ECDSAEdDSAPost-Quanten-Verfahren

ECDSA-Kurven
P-224P-256P-384P-521
EdDSA
Ed25519Ed25519phEd25519ctx
Formate öffentlicher Schlüssel
spki-derx509sec1-uncompressed
Formate privater Schlüssel
pkcs8-dersec1-derraw-scalar

Go liefert seine Kryptografie in der Standardbibliothek mit, und seit Go 1.24 stecken die zentralen Primitive im Go Cryptographic Module, das für FIPS 140-3 gebaut ist. crypto/ecdsa signiert und prüft, crypto/x509 liest Schlüssel und Zertifikate, und Go 1.27.3 bietet neben crypto/ed25519 auch crypto/mlkem (ML-KEM-768/1024) und crypto/mldsa (ML-DSA-44/65/87). Es gibt kein secp256k1: Code für Ethereum und Bitcoin nutzt die Pakete von go-ethereum oder decred.

Alles in crypto/ecdsa arbeitet mit einem Digest, den du selbst berechnet hast – das Paket hasht nie. Schlimmer als eine falsche Signatur: Eine lange Eingabe wird stillschweigend auf die Kurvengröße gekürzt, und eine Signatur über eine 46-Byte-Eingabe bestand die Prüfung für eine andere Eingabe mit denselben ersten 32 Byte. Hashe also immer zuerst, wie es das Beispiel tut. SignASN1, VerifyASN1 und die Schnittstelle crypto.Signer sprechen ASN.1-DER; WebCrypto, JWS und andere Gegenstellen mit rohem r‖s brauchen Sign/Verify mit Ganzzahlen, die per FillBytes aufgefüllt sind – Bytes() lässt führende Nullbytes weg, sodass jedes r oder s, das zufällig mit einem Nullbyte beginnt, ein Byte zu kurz herauskommt.

Signaturen sind randomisiert, und seit Go 1.26 wird der übergebene io.Reader ignoriert, sofern nicht GODEBUG=cryptocustomrand=1 gesetzt ist: Zwei Signaturen mit einem Reader, der nur Nullen liefert, unterschieden sich. Tests, die sich auf einen festen Reader verließen, um reproduzierbare Signaturen zu erhalten, funktionieren nicht mehr; der dokumentierte Ersatz ist testing/cryptotest.SetGlobalRandom. s wird nicht normalisiert, und VerifyASN1 akzeptiert beide Hälften; für Verifizierer, die strikt Low-S verlangen, muss s in den Signaturen daher durch n − s ersetzt werden.

Beim Parsen von Schlüsseln steht DER an erster Stelle. x509.ParsePKIXPublicKey scheitert bei PEM-Text mit asn1: structure error: tags don't match – rufe zuerst pem.Decode auf. ecdsa.ParseUncompressedPublicKey und ecdsa.ParseRawPrivateKey lesen den bloßen SEC1-Punkt und den bloßen Skalar; komprimierte Punkte werden abgelehnt, und für mit Passphrase verschlüsseltes PKCS#8 gibt es keinen Parser. Bei einer fehlerhaften Signatur gibt VerifyASN1 false zurück, nie einen Fehler.

Um eine Go-Signatur mit einem anderen Stack zu vergleichen, wandle sie im Konverter DER ⇄ Raw um und prüfe sie im Signatur-Verifizierer; Probleme beim Parsen von Zertifikaten erkennst du am leichtesten im Zertifikats-Decoder.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/go-crypto-ecdsa/run.sh hat Go crypto/ecdsa (standard library) 1.27.3 am 20. Oktober 2026 im Container-Image golang:1.27.3-trixie ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    der

    SignASN1, VerifyASN1 und PrivateKey.Sign (der Weg über crypto.Signer) verwenden ASN.1-DER; Sign und Verify nehmen r und s als *big.Int.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = der
  • ECDSA-Nonce

    random

    Randomisiert: Zwei Signaturen desselben Digests unterscheiden sich, wie es die Dokumentation des Pakets angibt (Entropie wird mit dem Schlüssel und der Nachricht gemischt).

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures with an all-zero Reader are identical: false
  • Argument für die Zufallsquelle

    ignored

    Seit Go 1.26 wird das Argument io.Reader ignoriert: Zwei Signaturen mit einem Reader, der nur Nullen liefert, waren verschieden. GODEBUG=cryptocustomrand=1 stellt das alte Verhalten vorerst wieder her.

    Von der Prüfung aufgezeichnet
    fact go-rand-reader-argument = ignored
  • Low-S beim Signieren

    either

    s wird nicht normalisiert: Unter 64 Signaturen traten High-S-Werte auf.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
  • High-S bei der Prüfung

    accepts-high-s

    VerifyASN1 akzeptierte die Signatur, in der s durch n − s ersetzt war.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
  • Was der Signaturaufruf hasht

    takes-digest

    Alle Funktionen nehmen einen Digest entgegen; nichts in crypto/ecdsa hasht die Nachricht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = takes-digest
  • Digest länger als die Kurvenordnung

    truncated

    Eine Eingabe, die länger als die Kurvenordnung ist, wird gekürzt: Eine Signatur über eine 46-Byte-Eingabe bestand die Prüfung für eine andere Eingabe mit denselben ersten 32 Byte.

    Von der Prüfung aufgezeichnet
    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
  • Fehlerhafte ECDSA-Signatur

    returns-false

    VerifyASN1 gab für eine rohe r‖s-Signatur und für einen 3-Byte-Blob false zurück.

    Von der Prüfung aufgezeichnet
    fact ecdsa-malformed-signature = returns-false
  • ECDSA-Kurven

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

    P-224, P-256, P-384 und P-521; crypto/elliptic bietet weder secp256k1 noch Brainpool-Kurven.

    Von der Prüfung aufgezeichnet
    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 sowie die Varianten Ed25519ph und Ed25519ctx aus RFC 8032 über ed25519.Options.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519, Ed25519ph, Ed25519ctx
  • Aufbau des privaten Schlüssels (Ed25519)

    expanded-64

    ed25519.PrivateKey umfasst 64 Byte, den Seed gefolgt vom öffentlichen Schlüssel; NewKeyFromSeed baut ihn aus dem 32-Byte-Seed.

    Von der Prüfung aufgezeichnet
    fact ed25519-private-key = expanded-64
  • Post-Quanten-Algorithmen

    ML-KEMML-DSA

    In Go 1.27.3 funktionierten Kapselung und Entkapselung mit ML-KEM-768 und ML-KEM-1024 sowie Signieren und Prüfen mit ML-DSA-44/65/87.

    Von der Prüfung aufgezeichnet
    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
  • Formate öffentlicher Schlüssel

    spki-derx509sec1-uncompressed

    SPKI als DER über x509.ParsePKIXPublicKey, Zertifikate und unkomprimierte Punkte im SEC1-Format über ecdsa.ParseUncompressedPublicKey; PEM braucht zuerst pem.Decode, und komprimierte Punkte werden abgelehnt.

    Von der Prüfung aufgezeichnet
    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
  • Formate privater Schlüssel

    pkcs8-dersec1-derraw-scalar

    PKCS#8 als DER, SEC1 als DER und der rohe Skalar über ecdsa.ParseRawPrivateKey; einen Parser für mit Passphrase verschlüsseltes PKCS#8 gibt es nicht.

    Von der Prüfung aufgezeichnet
    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

Verwendung

Hashe zuerst, signiere als DER für Gegenstellen im Stil von OpenSSL und baue für WebCrypto und JOSE ein korrekt aufgefülltes rohes r‖s.
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))
}

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image golang:1.27.3-trixie aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
go run .
Ausgabe muss passen (regulärer Ausdruck)
DER verified: true\nraw verified: true 64

Bibliotheken