Skip to content
ecdsa.com

PEM ⇄ DER · PHP

How to convert a certificate and a private key between PEM and DER in PHP

To convert a certificate or a key between PEM and DER in PHP, take the base64 between the BEGIN and END lines and base64_decode it in strict mode; to go back, base64_encode, split into 64-character lines with chunk_split and add the lines. No extension is needed for the conversion itself; this recipe checks the round trip and loads the results with ext/openssl.

Tested with PHP 8.5.11 (base64 functions, ext/openssl with OpenSSL 3.5.7) and OpenSSL 3.5.7 (reads the DER output).

convert.php — PEM ⇄ DER for any labelled block
<?php
// PEM is the base64 of the DER between two labelled lines: converting needs no parser
function pemToDer(string $pem, string $label): string
{
$quoted = preg_quote($label, '/');
if (!preg_match("/-----BEGIN $quoted-----(.+?)-----END $quoted-----/s", $pem, $match)) {
throw new InvalidArgumentException("no $label block");
}
$der = base64_decode($match[1], true); // strict: false for anything but base64 (whitespace is skipped)
if ($der === false) {
throw new InvalidArgumentException("invalid base64 in the $label block");
}
return $der;
}
function derToPem(string $der, string $label): string
{
return "-----BEGIN $label-----\n" . chunk_split(base64_encode($der), 64, "\n") . "-----END $label-----\n";
}

How it works

  1. pemToDer matches the block with the expected label and decodes it with base64_decode($text, true); strict mode returns false for any character outside base64 (whitespace is skipped).
  2. The DER is 563 bytes for the certificate and 138 for the PKCS#8 key; derToPem with chunk_split(…, 64, "\n") reproduces the original files exactly.
  3. openssl_x509_read and openssl_pkey_get_private load the converted PEM, which proves the bytes survived: subject /C=US/O=Example Corp/CN=api.example.com, curve prime256v1.
  4. The run also checks the DER with OpenSSL (openssl x509 -inform DER).

Gotchas

  • ext/openssl functions read PEM, not DER: openssl_x509_read($der) returns false. Wrap DER with derToPem before passing it.
  • Without the second argument, base64_decode silently skips invalid characters: base64_decode('TWF$u') returns 'Man', while strict mode returns false. A corrupted PEM then decodes to the wrong bytes instead of failing.

Related recipes