Skip to content
ecdsa.com

Create a PKCS#12 bundle · Java

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

To create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in Java, put the key and the chain into a KeyStore of type PKCS12 with setKeyEntry and save it with store(). KeyStore.load reads it back. This recipe uses only the JDK: it bundles a P-256 key with its certificate and issuing CA, reads the file again and checks it with OpenSSL.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1, KeyStore PKCS12) and OpenSSL 3.0.13 (reads the bundle back (inspect.sh), writes the legacy file).

CreateP12.java — PEM key and certificates into a PKCS12 KeyStore
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class CreateP12 {
public static void main(String[] args) throws Exception {
char[] password = System.getenv("P12_PASSWORD").toCharArray();
String keyPem = Files.readString(Path.of("key.pem"));
byte[] pkcs8 = Base64.getMimeDecoder().decode(keyPem.replaceAll("-----[^-]+-----", ""));
PrivateKey key = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(pkcs8));
CertificateFactory x509 = CertificateFactory.getInstance("X.509");
Certificate leaf, intermediate;
try (InputStream a = Files.newInputStream(Path.of("leaf.pem"));
InputStream b = Files.newInputStream(Path.of("intermediate.pem"))) {
leaf = x509.generateCertificate(a);
intermediate = x509.generateCertificate(b);
}
KeyStore p12 = KeyStore.getInstance("PKCS12");
p12.load(null, null); // a new, empty store
// The alias becomes the friendly name; the chain is leaf first
p12.setKeyEntry("api.example.com", key, password, new Certificate[] {leaf, intermediate});
try (FileOutputStream out = new FileOutputStream("bundle.p12")) {
p12.store(out, password);
}
}
}
ReadP12.java — load the store: aliases, keys, certificate chains
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
public class ReadP12 {
public static void main(String[] args) throws Exception {
char[] password = System.getenv("P12_PASSWORD").toCharArray();
KeyStore p12 = KeyStore.getInstance("PKCS12");
try (FileInputStream in = new FileInputStream("bundle.p12")) {
p12.load(in, password); // checks the MAC: a wrong password fails here
}
for (String alias : Collections.list(p12.aliases())) {
PrivateKey key = (PrivateKey) p12.getKey(alias, password);
Certificate[] chain = p12.getCertificateChain(alias);
System.out.println("alias " + alias + ": " + key.getAlgorithm() + " key, chain of " + chain.length);
for (Certificate cert : chain) {
System.out.println(" " + ((X509Certificate) cert).getSubjectX500Principal().getName());
}
}
}
}

How it works

  1. KeyStore.getInstance("PKCS12") followed by load(null, null) creates an empty store; setKeyEntry(alias, key, password, chain) adds the key with its chain, leaf first.
  2. store(out, password) writes the file. JDK 25 protects it with PBES2 — PBKDF2-HMAC-SHA256 and AES-256-CBC, 10 000 iterations — and an HMAC-SHA-256 MAC, as OpenSSL's -info confirms in the run.
  3. The alias becomes the PKCS#12 friendly name (api.example.com), which is what keytool -list and OpenSSL show.
  4. Reading is load(in, password) followed by getKey(alias, password) and getCertificateChain(alias).

Gotchas

  • A wrong password fails in load() with IOException: keystore password was incorrect, before any entry is touched.
  • The JDK reads old RC2-protected files without any switch — the run loads one written by openssl pkcs12 -legacy — while OpenSSL 3 needs -legacy for them. A Java service that reads a bundle fine is no proof that OpenSSL-based software can.
  • The protection follows the JDK: JDK 25 writes AES-256 with 10 000 iterations, as -info shows, while older JDKs wrote RC2 and 3DES. Check what a long-lived build server actually produces before you hand the file to other software.

Related recipes