Skip to content
ecdsa.com

Create a PKCS#12 bundle · PHP

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

To create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in PHP, call openssl_pkcs12_export_to_file with the certificate, the key, the password and the chain in 'extracerts'; openssl_pkcs12_read reads it back into PEM strings. This recipe bundles a P-256 key with its certificate and issuing CA and checks the file with OpenSSL.

Tested with PHP 8.5.11 (ext/openssl, OpenSSL 3.5.7) and OpenSSL 3.5.7 (reads the bundle back (inspect.sh), writes the legacy file).

create.php — certificate + key + chain into bundle.p12
<?php
$password = getenv('P12_PASSWORD');
$ok = openssl_pkcs12_export_to_file(
file_get_contents('leaf.pem'), // the certificate
'bundle.p12',
file_get_contents('key.pem'), // its private key
$password,
[
'extracerts' => [file_get_contents('intermediate.pem')], // the chain, without the leaf
'friendly_name' => 'api.example.com',
],
);
if (!$ok) {
throw new RuntimeException('PKCS#12 export failed: ' . openssl_error_string());
}
read.php — read the bundle back into PEM strings
<?php
$password = getenv('P12_PASSWORD');
if (!openssl_pkcs12_read(file_get_contents('bundle.p12'), $bundle, $password)) {
throw new RuntimeException('cannot read bundle.p12: ' . openssl_error_string());
}
// $bundle['pkey'] and $bundle['cert'] are PEM strings, $bundle['extracerts'] a list of them
echo 'certificate: ', openssl_x509_parse($bundle['cert'])['name'], "\n";
foreach ($bundle['extracerts'] ?? [] as $extra) {
echo 'chain: ', openssl_x509_parse($extra)['name'], "\n";
}
echo 'key matches certificate: ', var_export(openssl_x509_check_private_key($bundle['cert'], $bundle['pkey']), true), "\n";
echo 'wrong password: ', var_export(openssl_pkcs12_read(file_get_contents('bundle.p12'), $unused, 'wrong-password'), true);
echo ' (', openssl_error_string(), ")\n";

How it works

  1. openssl_pkcs12_export_to_file($cert, $file, $key, $password, $options) writes the bundle; extracerts holds the chain without the leaf, friendly_name the alias.
  2. The protection is OpenSSL's default: with OpenSSL 3.5 that is AES-256-CBC with PBKDF2-HMAC-SHA256 and an HMAC-SHA-256 MAC, 2048 iterations.
  3. openssl_pkcs12_read($data, $out, $password) fills $out['pkey'], $out['cert'] and $out['extracerts'] with PEM strings that every other ext/openssl function accepts.
  4. openssl_x509_check_private_key confirms the key belongs to the certificate.

Gotchas

  • Old RC2-protected files cannot be read on OpenSSL 3: openssl_pkcs12_read returns false with error:0308010C:digital envelope routines::unsupported (the run reads a file written by openssl pkcs12 -legacy). Convert such files once with OpenSSL's -legacy switch.
  • Both functions return false on failure and never throw; check the result and read openssl_error_string() — a wrong password reports error:11800071:PKCS12 routines::mac verify failure.

Related recipes