Skip to content
ecdsa.com

PEM ⇄ DER · Go

How to convert a certificate and a private key between PEM and DER in Go

To convert a certificate or a key between PEM and DER in Go, use encoding/pem: pem.Decode returns the DER bytes of a block and pem.EncodeToMemory wraps DER in a labelled block. No parsing is involved, so the same two functions work for certificates, PKCS#8 keys and public keys; this recipe converts both ways and checks the result with crypto/x509 and OpenSSL.

Tested with Go 1.27.3 (encoding/pem, linux/arm64) and OpenSSL 3.5.7 (reads the DER output).

convert.go — PEM ⇄ DER for any labelled block
package main
import (
"encoding/pem"
"fmt"
)
// pemToDER returns the bytes of the first PEM block, which must carry the expected label.
func pemToDER(data []byte, label string) ([]byte, error) {
block, _ := pem.Decode(data) // the second result is whatever follows the block
if block == nil {
return nil, fmt.Errorf("no PEM block found")
}
if block.Type != label {
return nil, fmt.Errorf("PEM block is %q, want %q", block.Type, label)
}
return block.Bytes, nil // already DER: PEM is base64 of DER plus a label
}
// derToPEM wraps DER bytes in a PEM block ("CERTIFICATE", "PRIVATE KEY", "PUBLIC KEY", ...).
func derToPEM(der []byte, label string) []byte {
return pem.EncodeToMemory(&pem.Block{Type: label, Bytes: der})
}

How it works

  1. pem.Decode(data) returns the first *pem.Block and the rest of the input; block.Bytes already is the DER — 563 bytes for the certificate, 138 for the PKCS#8 key.
  2. Check block.Type against the label you expect (CERTIFICATE, PRIVATE KEY, PUBLIC KEY …) before handing the bytes to a parser.
  3. pem.EncodeToMemory(&pem.Block{Type: label, Bytes: der}) writes 64-character base64 lines; the round trip reproduces the original files exactly.
  4. Parse the DER with the function that matches its label: x509.ParseCertificate, x509.ParsePKCS8PrivateKey, x509.ParsePKIXPublicKey.

Gotchas

  • pem.Decode returns nil for input without a PEM block — no error value — so check it, as pemToDER does (no PEM block found).
  • x509.ParseECPrivateKey reads only SEC1 (EC PRIVATE KEY); on PKCS#8 DER it fails with x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format).
  • pem.Decode returns only the first block; loop on its second return value to reach every certificate of a bundle — the run finds both blocks of a two-certificate file that way.

Related recipes