Formats and PKCS#12

OpenSSL PKCS#12 (.pfx) commands and the 3.0 -legacy change

By · Last verified Tested macOS · OpenSSL 3.6.1

openssl pkcs12 creates and reads PKCS#12 (.pfx, .p12) files, which hold a private key, its certificate and the chain under one password. The main pitfall is OpenSSL 3.0: new files use AES-256 and an SHA-256 MAC that older Windows, macOS and Java may reject, and old RC2 files need -legacy to open. Outputs are from OpenSSL 3.6.1.

For plain format conversions (PFX to PEM, PFX to JKS) see convert formats, or use the in-browser SSL converter.

Create a PFX with the key, certificate and chain

-inkey is the key, -in the leaf certificate, -certfile the intermediates and -name the friendly name shown on import.

openssl pkcs12 -export -inkey server.key -in server.crt -certfile intermediate.crt \
  -name "example.com" -out example.pfx
Enter Export Password:
Verifying - Enter Export Password:

The friendly name becomes the alias in Java keystores and the label in the Windows certificate store. If the key does not belong to the certificate, OpenSSL stops with No cert in -in file 'server.crt' matches private key. You can also pass a single fullchain.pem with -in; OpenSSL picks the certificate matching the key as the leaf.

Let OpenSSL build the chain

-chain builds the path from the leaf to a trusted root and includes every certificate on it.

openssl pkcs12 -export -inkey server.key -in server.crt -chain \
  -untrusted intermediate.crt -CAfile root.crt -name example.com -out example.pfx
openssl pkcs12 -in example.pfx -nokeys -passin pass:changeit | grep subject=
subject=CN=example.com
subject=CN=Example Test Intermediate CA
subject=CN=Example Test Root CA

Without a usable root, -chain fails with Error getting chain: unable to get local issuer certificate. For public certificates, the system trust store is used when you omit -CAfile. -chain also adds the root, which servers do not need to send.

Check which algorithms a PFX uses

-info -noout prints the MAC and encryption algorithms without writing any keys.

openssl pkcs12 -in example.pfx -info -noout
MAC: sha256, Iteration 2048
MAC length: 32, salt length: 16
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256

This is the OpenSSL 3.x default. A legacy file shows MAC: sha1 and pbeWithSHA1And40BitRC2-CBC or pbeWithSHA1And3-KeyTripleDES-CBC instead. Run this first whenever an import fails: it tells you which side needs to change.

Understand the OpenSSL 3.0 default change

OpenSSL 3.0 switched PKCS#12 export from legacy PKCS#12 PBE algorithms to PBES2 with AES-256.

OpenSSL 1.1.1 and older OpenSSL 3.0 and later (default) 3.x with -legacy
Certificate encryption RC2-40-CBC AES-256-CBC, PBKDF2, HMAC-SHA256 RC2-40-CBC (or 3DES if RC2 is disabled)
Private key encryption 3DES AES-256-CBC, PBKDF2, HMAC-SHA256 3DES
MAC SHA-1 SHA-256 SHA-1
Iterations 2048 2048 2048

RC2 and other old ciphers moved into the legacy provider in 3.0, which is not loaded by default. That is why the same command that worked for years now produces files some importers refuse, and why 3.x cannot open files made by older tools until you add -legacy.

Create a PFX for old Windows, macOS or Java

Add -legacy to get the pre-3.0 algorithms.

openssl pkcs12 -export -legacy -inkey server.key -in server.crt -certfile intermediate.crt \
  -name example.com -out example-legacy.pfx
openssl pkcs12 -in example-legacy.pfx -info -noout -legacy
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048

Use it when Windows Server 2016 or older, an old macOS Keychain, older Java 8 builds, or network appliances report a wrong password for a file you know is correct. If OpenSSL was installed outside the system paths, -legacy may also need -provider-path or the OPENSSL_MODULES environment variable to find legacy.so.

Choose the algorithms explicitly

-certpbe, -keypbe and -macalg select algorithms without loading the legacy provider.

openssl pkcs12 -export -inkey server.key -in server.crt -certfile intermediate.crt \
  -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1 \
  -name example.com -out example-compat.pfx
openssl pkcs12 -in example-compat.pfx -info -noout
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048
Certificate bag
Certificate bag
PKCS7 Data
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048

3DES is still in the default provider, so this file is readable by OpenSSL 3.x without -legacy and by nearly every older importer. It is the best compromise when you do not know what will read the file. -iter 100000 raises the iteration count to slow down password guessing.

Read a legacy PFX on OpenSSL 3.x

An unsupported error naming RC2-40-CBC means the file uses legacy algorithms; add -legacy.

openssl pkcs12 -in old.pfx -info -noout
openssl pkcs12 -in old.pfx -info -noout -legacy
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...:Algorithm (RC2-40-CBC : 0), Properties ()

MAC: sha1, Iteration 2048
...
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048

The same 0308010C ... unsupported error appears in Node.js 17+, Python and other software linked against OpenSSL 3. To fix it permanently, re-export once with -legacy on input and default (AES) on output, as shown in "Change the password" below.

Fix "Mac verify error: invalid password?"

This message means the MAC did not verify: almost always a wrong password, occasionally an unsupported MAC.

openssl pkcs12 -in example.pfx -noout -passin pass:wrong
Mac verify error: invalid password?

Libraries report the same condition as mac verify failure. Check for an empty password (-passin pass:), a trailing newline in a password file, or non-ASCII characters that the exporting tool encoded differently. If the password is definitely right and the file came from a very new system, an older reader may not know the MAC algorithm (for example PBMAC1, which OpenSSL 3.4 and later can write with -pbmac1_pbkdf2).

Extract the key, certificate and CA certificates

-nocerts, -clcerts -nokeys and -cacerts -nokeys separate the parts.

openssl pkcs12 -in example.pfx -nocerts -noenc -out example.com.key
openssl pkcs12 -in example.pfx -clcerts -nokeys -out example.com.crt
openssl pkcs12 -in example.pfx -cacerts -nokeys -out chain.crt
head -3 example.com.key
Bag Attributes
    localKeyID: 12 21 0D 64 22 E9 E3 EB CC 6B 65 BE B6 A8 CA 17 5F 63 39 3A 
    friendlyName: example.com

-clcerts outputs only the certificate that matches the key, which avoids picking the wrong one from an unordered file. Without -noenc the key is re-encrypted with a new PEM passphrase. Add -legacy to each command for legacy files.

Create a certificate-only PKCS#12 truststore

Use -nokeys to package CA certificates for Java or other clients that need a truststore.

openssl pkcs12 -export -nokeys -in root.crt -jdktrust anyExtendedKeyUsage \
  -out truststore.p12
openssl pkcs12 -in truststore.p12 -nokeys | head -2
Bag Attributes
    Trusted key usage (Oracle): Any Extended Key Usage (2.5.29.37.0)

Java only treats a certificate in a PKCS#12 store as a trusted entry when it carries Oracle's trusted-key-usage attribute, which -jdktrust (OpenSSL 3.2 and later) adds. On older OpenSSL versions build the truststore with keytool -importcert instead.

Create a PFX without a password

An empty -passout pass: still encrypts with an empty password; -keypbe NONE -certpbe NONE -nomac stores everything in clear.

openssl pkcs12 -export -inkey server.key -in server.crt -passout pass: -out nopass.pfx
openssl pkcs12 -export -inkey server.key -in server.crt \
  -keypbe NONE -certpbe NONE -nomac -passout pass: -out plain.pfx
openssl pkcs12 -in plain.pfx -info -noout -passin pass:
Warning: MAC is absent!
PKCS7 Data
Certificate bag
PKCS7 Data
Key bag

Some software (for example certain container platforms) expects a password-less PFX. The unencrypted variant is a plain private key in a different wrapper, so protect it like a .key file. -nomac is also used with the FIPS provider, which cannot compute the PKCS#12 MAC.

Change the password of a PFX

Export to a temporary PEM, re-export with the new password, then delete the temporary file.

umask 077
openssl pkcs12 -in example.pfx -noenc -passin file:old.txt -out tmp-all.pem
openssl pkcs12 -export -in tmp-all.pem -name example.com -passout file:new.txt -out example-new.pfx
rm -f tmp-all.pem
(no output on success)

Piping the first command into the second does not work, because pkcs12 -export reads its input twice and stdin can only be read once (Could not find certificates from -in file from <stdin>). Add -legacy to the first command to upgrade an old RC2 file to AES at the same time.

Run pkcs12 in scripts without prompts

Pass passwords through file:, env: or fd: instead of typing them.

P12PASS='s3cret' openssl pkcs12 -in example.pfx -noout -passin env:P12PASS && echo ok
openssl pkcs12 -export -inkey server.key -in server.crt -passout file:pfx-pass.txt -out example.pfx
ok

pass:secret works too but shows up in ps output and shell history. -passin is the password for reading a PFX (or the input key during -export); -passout is the password for the file being written. See troubleshooting for more OpenSSL error messages.

FAQ

Is -legacy insecure?

The legacy algorithms (RC2-40, 3DES, SHA-1 MAC) are weak against password guessing on a stolen file, so treat a legacy PFX like plaintext: use a long random password, keep it off shared storage and delete it after import. Use the AES default whenever the importing system supports it.

Do .pfx and .p12 files differ?

No. Both are PKCS#12. Windows tools tend to use .pfx and Java and macOS tools .p12; you can rename one to the other.

Why does my PFX import without the intermediate certificate?

It was exported without the chain. Re-export with -certfile intermediate.crt, or use -chain with -untrusted so OpenSSL builds and includes the chain itself.

Sources

  1. OpenSSL 3.0 manual: openssl-pkcs12
  2. OpenSSL 3.5 manual: openssl-pkcs12
  3. OpenSSL 3.0 manual: OSSL_PROVIDER-legacy
  4. OpenSSL 3.0 migration guide
  5. RFC 7292: PKCS #12 v1.1