package main import ( "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/pem" "fmt" "os") func main() { pemBytes, _ := os.ReadFile("pub.pem") block, _ := pem.Decode(pemBytes) // "PUBLIC KEY" — SPKI pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { panic(err) } ecPub := pub.(*ecdsa.PublicKey) message, _ := os.ReadFile("message.txt") signature, _ := os.ReadFile("sig.der") // ASN.1 DER digest := sha256.Sum256(message) // Go verifies a digest, not the message if ecdsa.VerifyASN1(ecPub, digest[:], signature) { fmt.Println("valid") } else { fmt.Println("INVALID") }}r := new(big.Int).SetBytes(rawSig[:32]) // needs "math/big"s := new(big.Int).SetBytes(rawSig[32:])ok := ecdsa.Verify(ecPub, digest[:], r, s)How it works
pem.Decodeextracts the DER block fromBEGIN PUBLIC KEY;x509.ParsePKIXPublicKeyparses the SPKI structure and returnsany— type-assert to*ecdsa.PublicKey.- Unlike Node.js and Python, Go does not hash for you: compute
sha256.Sum256(message)and pass the digest slice. ecdsa.VerifyASN1consumes ASN.1 DER (OpenSSL's output). For raw 64-byte signatures, load each half into abig.Intand callecdsa.Verify.
Gotchas
- Passing the message where the digest belongs is the classic Go ECDSA bug — verification simply returns
false. The digest argument should be exactly the hash size (32 bytes for SHA-256). - If your PEM says
BEGIN CERTIFICATE, parse it withx509.ParseCertificateand usecert.PublicKey—ParsePKIXPublicKeydoes not accept certificates. VerifyASN1uses a strict DER parser: trailing bytes or non-minimal integers are rejected. A signature that passes elsewhere but fails in Go may be sloppily encoded — inspect it in the converter.