ecdsa.com

Export public key PEM · Go

How to export a public key as PEM in Go

Go exports an ECDSA public key as PEM with two standard-library calls: x509.MarshalPKIXPublicKey builds the SPKI DER, and pem.Encode wraps it under the PUBLIC KEY label. The private key struct already embeds the public half — no derivation step needed.

Tested with Go 1.26 (standard library only).

export_pub.go — private PEM in, public PEM out
package main
 
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"os"
)
 
func main() {
pemBytes, _ := os.ReadFile("key.pem")
block, _ := pem.Decode(pemBytes)
key, err := x509.ParsePKCS8PrivateKey(block.Bytes) // "BEGIN PRIVATE KEY"
if err != nil {
panic(err)
}
priv := key.(*ecdsa.PrivateKey)
 
spki, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
panic(err)
}
 
out, err := os.Create("pub.pem")
if err != nil {
panic(err)
}
defer out.Close()
pem.Encode(out, &pem.Block{Type: "PUBLIC KEY", Bytes: spki})
}

How it works

  1. priv.PublicKey is an embedded struct field — pass its address to MarshalPKIXPublicKey, which accepts *ecdsa.PublicKey among other types.
  2. "PKIX" in the function name is SPKI: the output DER is the body of a standard BEGIN PUBLIC KEY block.
  3. pem.Encode handles the 64-column base64 layout and header/footer lines; the block Type string becomes the PEM label verbatim.
  4. A quick cross-tool self-check after writing the file: openssl pkey -pubin -in pub.pem -noout exits 0 only when the SPKI parses cleanly.

Gotchas

  • Passing the struct by value (priv.PublicKey instead of &priv.PublicKey) fails marshalling — the function switches on pointer types.
  • A legacy BEGIN EC PRIVATE KEY input needs x509.ParseECPrivateKey at the load step; everything after the parse is identical.
  • Misspelling the block type (e.g. "EC PUBLIC KEY") produces a file OpenSSL and every other stack rejects — the SPKI container's label is exactly PUBLIC KEY.

Related recipes