<?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-onlyfile_put_contents('key.pem', $privatePem);umask($umask);file_put_contents('pub.pem', $publicPem);echo $publicPem; <?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
openssl_pkey_newtakes the key type andcurve_name;openssl_get_curve_names()lists every name the linked OpenSSL accepts.openssl_pkey_exportwrites PEM into its second argument; with OpenSSL 3 that is PKCS#8,BEGIN PRIVATE KEY.openssl_pkey_get_details($key)['key']is the public key as SPKI PEM; the same array holds the curve name and the coordinates underec.umask(0077)aroundfile_put_contentscreates key.pem with mode 600 — the run checks the mode and that OpenSSL derives the same public key from it.- With a passphrase and
encrypt_key_cipher, the export becomesBEGIN ENCRYPTED PRIVATE KEY, whichopenssl_pkey_get_private($pem, $passphrase)andopenssl pkey -passinboth open.
Gotchas
- Only OpenSSL short names work:
'prime256v1'for P-256,'secp384r1','secp521r1','secp256k1'.'P-256'and even'secp256r1'makeopenssl_pkey_newreturnfalse. - After a successful
openssl_pkey_newthe OpenSSL error queue may still holdrandom number generator::Cannot open file(OpenSSL looks for a~/.rndseed file). It is harmless, but code that treats anyopenssl_error_string()as failure misfires — check the return value instead. openssl_pkey_get_detailson a private key also returns the secret scalar as['ec']['d']. Take only['key']from it, and never log or serialize the whole array.umaskis process-wide; the PHP manual advises against it in multithreaded web servers. There, create the file withfopen,chmodit to 0600, and only then write the key.