Skip to content
ecdsa.com

Create a PKCS#12 bundle · OpenSSL

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

To create a PKCS#12 (.p12/.pfx) file from a key and certificate chain with OpenSSL, run openssl pkcs12 -export with the key, the certificate and -certfile for the intermediates; openssl pkcs12 without -export reads it back to PEM. This recipe bundles a P-256 key, its certificate and the issuing CA with a password, inspects the protection OpenSSL 3 applies, and shows when the -legacy switch is needed.

Tested with OpenSSL 3.5.7 (Debian 13, legacy provider for -legacy).

Same recipe in:PythonGoOpenSSLJava.NETRustPHPRubyKotlin
key + certificate + chain → bundle.p12
# Key + certificate + chain into one password-protected file (AES-256 with OpenSSL 3)
openssl pkcs12 -export -inkey key.pem -in leaf.pem -certfile intermediate.pem \
-name "api.example.com" -passout env:P12_PASSWORD -out bundle.p12
bundle.p12 → PEM: everything, only the chain, only the key
# Back to PEM: the key (unencrypted with -nodes) and every certificate
openssl pkcs12 -in bundle.p12 -passin env:P12_PASSWORD -nodes -out everything.pem
grep -E '^-----BEGIN' everything.pem
# Only the certificates, or only the key
openssl pkcs12 -in bundle.p12 -passin env:P12_PASSWORD -nokeys -out chain.pem
openssl pkcs12 -in bundle.p12 -passin env:P12_PASSWORD -nocerts -nodes -out bundle-key.pem
read and write files that use RC2 or 3DES
# Files from older tools use RC2/3DES, which OpenSSL 3 only reads with -legacy
openssl pkcs12 -in old.p12 -passin env:P12_PASSWORD -legacy -nodes -out old.pem
# ...and the same switch writes such a file for software that cannot read AES
openssl pkcs12 -export -legacy -inkey key.pem -in leaf.pem -certfile intermediate.pem \
-name "api.example.com" -passout env:P12_PASSWORD -out old.p12

How it works

  1. -inkey is the private key, -in the certificate it belongs to, -certfile the chain (intermediates, optionally the root); -name sets the friendly name that keystores show as the alias.
  2. -passout env:P12_PASSWORD reads the password from an environment variable, so it never appears in the command line or the shell history; -passin does the same for reading.
  3. OpenSSL 3 protects the bundle with PBES2 — PBKDF2 with HMAC-SHA-256 and AES-256-CBC — for both the key and the certificates, plus an HMAC-SHA-256 MAC over the whole file, 2048 iterations each.
  4. Reading with -nodes writes the key unencrypted; -nokeys and -nocerts select one part. The run checks that the key in the bundle belongs to leaf.pem and that a wrong password fails with Mac verify error: invalid password?.

Gotchas

  • Files from OpenSSL 1.x, older Java and Windows use 40-bit RC2 or 3DES. OpenSSL 3 refuses to read them without -legacy — the error names the algorithm: unsupported … Algorithm (RC2-40-CBC : 0). Add -legacy; it needs the legacy provider, which Debian ships with OpenSSL.
  • -legacy also writes such files (40-bit RC2 for the certificates in the run) for readers that only know the old algorithms. Prefer upgrading the reader: RC2-40 offers no real protection.
  • -nodes ("no DES") writes the private key without a password. Keep the output out of version control and set its permissions as tight as the bundle's.
  • -certfile is how the chain gets in: without it the bundle holds only the leaf, and servers that load it send an incomplete chain.

Related recipes