Skip to content
ecdsa.com

Generate a key pair · PHP

How to generate an ECDSA key pair in PHP

To generate an ECDSA key pair in PHP, call openssl_pkey_new with OPENSSL_KEYTYPE_EC and a curve name, then export both halves: openssl_pkey_export writes the PKCS#8 private key, openssl_pkey_get_details returns the SPKI public key PEM. Everything is in the OpenSSL extension that ships with PHP — the only trap is the curve name.

Tested with PHP 8.5.11 (ext/openssl, OpenSSL 3.5.7) and OpenSSL 3.5.7 (openssl pkey cross-check).

generate.php — a P-256 key pair written as PEM files
<?php
$key = openssl_pkey_new([
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1', // P-256 under its OpenSSL name; 'P-256' is not accepted
]);
if ($key === false) {
throw new RuntimeException('key generation failed: ' . openssl_error_string());
}
openssl_pkey_export($key, $privatePem); // "BEGIN PRIVATE KEY" (PKCS#8)
$publicPem = openssl_pkey_get_details($key)['key']; // "BEGIN PUBLIC KEY" (SPKI)
$umask = umask(0077); // the private key file is created owner-only
file_put_contents('key.pem', $privatePem);
umask($umask);
file_put_contents('pub.pem', $publicPem);
echo $publicPem;
encrypted.php — a passphrase-protected private key
<?php
// "BEGIN ENCRYPTED PRIVATE KEY": the same export, with a passphrase and a cipher
$key = openssl_pkey_new(['private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']);
openssl_pkey_export($key, $encryptedPem, getenv('KEY_PASSPHRASE'), [
'encrypt_key_cipher' => OPENSSL_CIPHER_AES_256_CBC,
]);
file_put_contents('key-encrypted.pem', $encryptedPem);
file_put_contents('pub-encrypted.pem', openssl_pkey_get_details($key)['key']);

How it works

  1. openssl_pkey_new takes the key type and curve_name; openssl_get_curve_names() lists every name the linked OpenSSL accepts.
  2. openssl_pkey_export writes PEM into its second argument; with OpenSSL 3 that is PKCS#8, BEGIN PRIVATE KEY.
  3. openssl_pkey_get_details($key)['key'] is the public key as SPKI PEM; the same array holds the curve name and the coordinates under ec.
  4. umask(0077) around file_put_contents creates key.pem with mode 600 — the run checks the mode and that OpenSSL derives the same public key from it.
  5. With a passphrase and encrypt_key_cipher, the export becomes BEGIN ENCRYPTED PRIVATE KEY, which openssl_pkey_get_private($pem, $passphrase) and openssl pkey -passin both open.

Gotchas

  • Only OpenSSL short names work: 'prime256v1' for P-256, 'secp384r1', 'secp521r1', 'secp256k1'. 'P-256' and even 'secp256r1' make openssl_pkey_new return false.
  • After a successful openssl_pkey_new the OpenSSL error queue may still hold random number generator::Cannot open file (OpenSSL looks for a ~/.rnd seed file). It is harmless, but code that treats any openssl_error_string() as failure misfires — check the return value instead.
  • openssl_pkey_get_details on a private key also returns the secret scalar as ['ec']['d']. Take only ['key'] from it, and never log or serialize the whole array.
  • umask is process-wide; the PHP manual advises against it in multithreaded web servers. There, create the file with fopen, chmod it to 0600, and only then write the key.

Related recipes