<?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
pemToDermatches the block with the expected label and decodes it withbase64_decode($text, true); strict mode returnsfalsefor any character outside base64 (whitespace is skipped).- The DER is 563 bytes for the certificate and 138 for the PKCS#8 key;
derToPemwithchunk_split(…, 64, "\n")reproduces the original files exactly. openssl_x509_readandopenssl_pkey_get_privateload the converted PEM, which proves the bytes survived: subject/C=US/O=Example Corp/CN=api.example.com, curveprime256v1.- The run also checks the DER with OpenSSL (
openssl x509 -inform DER).
Gotchas
- ext/openssl functions read PEM, not DER:
openssl_x509_read($der)returnsfalse. Wrap DER withderToPembefore passing it. - Without the second argument,
base64_decodesilently skips invalid characters:base64_decode('TWF$u')returns'Man', while strict mode returnsfalse. A corrupted PEM then decodes to the wrong bytes instead of failing.
Related recipes
- How to convert a certificate and a private key between PEM and DER in Ruby
- How to convert a certificate and a private key between PEM and DER in Python
- How to convert a certificate and a private key between PEM and DER with OpenSSL
- How to export a public key as PEM in PHP
- How to decode an X.509 certificate in PHP