Skip to content
ecdsa.com

Create a PKCS#12 bundle · Go

How to create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in Go

To create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in Go, use go-pkcs12 (software.sslmate.com/src/go-pkcs12): pkcs12.Modern.Encode writes key, certificate and chain with AES-256 and HMAC-SHA-256, and pkcs12.DecodeChain reads them back. The standard library has no PKCS#12 support; this recipe bundles a P-256 key with its certificate and issuing CA and checks the file with OpenSSL.

Tested with go-pkcs12 0.7.4 (Go 1.27.3, software.sslmate.com/src/go-pkcs12) and OpenSSL 3.5.7 (reads the bundle back (inspect.sh)).

bundle.go — encode with modern protection, decode key and chain
package main
import (
"crypto"
"crypto/x509"
"fmt"
"software.sslmate.com/src/go-pkcs12"
)
// createBundle writes key, certificate and chain with modern protection:
// AES-256-CBC with PBKDF2 for the contents, HMAC-SHA-256 for integrity.
func createBundle(key crypto.PrivateKey, leaf *x509.Certificate, chain []*x509.Certificate, password string) ([]byte, error) {
return pkcs12.Modern.Encode(key, leaf, chain, password)
}
// readBundle decodes the file again: the key, the leaf certificate and the rest of the chain.
func readBundle(data []byte, password string) error {
key, leaf, chain, err := pkcs12.DecodeChain(data, password)
if err != nil {
return err
}
fmt.Println("certificate:", leaf.Subject)
for _, c := range chain {
fmt.Println("chain:", c.Subject)
}
public := key.(crypto.Signer).Public().(interface{ Equal(crypto.PublicKey) bool })
fmt.Println("key matches certificate:", public.Equal(leaf.PublicKey))
return nil
}

How it works

  1. pkcs12.Modern.Encode(key, leaf, chain, password) produces the file: PBES2 with PBKDF2 and AES-256-CBC for key and certificates, an HMAC-SHA-256 MAC over the whole.
  2. pkcs12.DecodeChain(data, password) returns the key, the leaf certificate and the rest of the chain.
  3. The key comes back as crypto.PrivateKey; Public().Equal(leaf.PublicKey) confirms it belongs to the certificate.
  4. golang.org/x/crypto/pkcs12 is frozen and can only decode; go-pkcs12 is the maintained fork that also encodes.

Gotchas

  • pkcs12.Decode (singular) expects exactly one key and one certificate; on a bundle with a chain it fails with pkcs12: expected exactly two safe bags in the PFX PDU. Use DecodeChain.
  • go-pkcs12 writes no friendly name: OpenSSL shows an empty friendlyName for the key. Tools that select the key by alias need it set by other means.
  • pkcs12.Legacy (today the same as LegacyDES) writes 3DES — pbeWithSHA1And3-KeyTripleDES-CBC in the run — for software that cannot read AES; use Modern unless such a reader forces you.
  • A wrong password returns pkcs12: decryption password incorrect; the same error comes back for a damaged file.

Related recipes