Skip to content
ecdsa.com

Public key from a certificate · Go

How to extract the public key from an X.509 certificate in Go

To extract the public key from an X.509 certificate in Go, parse it with crypto/x509 and take cert.RawSubjectPublicKeyInfo — the SPKI bytes exactly as stored in the certificate — or the parsed cert.PublicKey. This recipe writes the key as a PEM "PUBLIC KEY" block, computes its SHA-256 fingerprint and base64 pin, and checks that a private key belongs to the certificate.

Tested with Go 1.27.3 (crypto/x509, linux/arm64).

main.go — SPKI PEM, fingerprint, pin and the key-pair check
package main
import (
"bytes"
"crypto"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"log"
"os"
"strings"
)
func main() {
certPEM, err := os.ReadFile("cert.pem")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(certPEM)
if block == nil || block.Type != "CERTIFICATE" {
log.Fatal("cert.pem: no CERTIFICATE block")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
log.Fatal(err)
}
// RawSubjectPublicKeyInfo: the SPKI bytes exactly as they appear in the certificate
spki := cert.RawSubjectPublicKeyInfo
pem.Encode(os.Stdout, &pem.Block{Type: "PUBLIC KEY", Bytes: spki})
sum := sha256.Sum256(spki)
hexPairs := make([]string, len(sum))
for i, b := range sum {
hexPairs[i] = fmt.Sprintf("%02x", b)
}
fmt.Println("SPKI SHA-256:", strings.Join(hexPairs, ":"))
fmt.Println("SPKI pin (base64):", base64.StdEncoding.EncodeToString(sum[:]))
// cert.PublicKey is the parsed key (*ecdsa.PublicKey here); MarshalPKIXPublicKey re-encodes it
remarshaled, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
log.Fatal(err)
}
fmt.Println("MarshalPKIXPublicKey gives the same bytes:", bytes.Equal(remarshaled, spki))
// Does key.pem belong to this certificate?
keyPEM, err := os.ReadFile("key.pem")
if err != nil {
log.Fatal(err)
}
keyBlock, _ := pem.Decode(keyPEM)
key, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
if err != nil {
log.Fatal(err)
}
pub := key.(crypto.Signer).Public().(interface{ Equal(crypto.PublicKey) bool })
fmt.Println("key.pem matches:", pub.Equal(cert.PublicKey))
}

How it works

  1. x509.ParseCertificate(block.Bytes) gives cert.RawSubjectPublicKeyInfo, the DER of the SubjectPublicKeyInfo; pem.Encode with type PUBLIC KEY turns it into the standard PEM.
  2. SHA-256 over those bytes is the key fingerprint (45:ca:a7:e3:…:7b:5f), and its base64 (Rcqn43J0…e18=) the SPKI pin.
  3. cert.PublicKey is the parsed key (*ecdsa.PublicKey here); x509.MarshalPKIXPublicKey encodes it back to SPKI — the same bytes for this certificate.
  4. Every public key type in the standard library has Equal, so privateKey.Public().Equal(cert.PublicKey) checks that key.pem belongs to the certificate.

Gotchas

  • cert.PublicKey has type any; type-switch on it (*ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey, and *mldsa.PublicKey since Go 1.26) before using algorithm-specific fields.
  • The certificate's SHA-256 (sha256.Sum256(cert.Raw)) is the certificate fingerprint; it changes at every renewal, the SPKI hash does not.

Related recipes