Team LiB
Previous Section Next Section

Using Public Keys in PHP

PHP includes support for the widely used and maintained OpenSSL library, which does nearly all the public key cryptography heavy lifting for you. You don't need to worry about how to generate public and private keys. In most cases you don't even need to worry about when to apply one over the other.

We'll start by looking at the easiest to use API for secure network communication and move on to encrypting and/or signing data for file storage or asynchronous data transfer.

SSL Streams

If you're already familiar with opening TCP/IP network streams and sending data to and/or reading data from a network socket, you're already halfway to applying encryption to that network data. Consider this simple HTTP client:

$conn = @fsockopen('tcp://', 80);
If (!$conn) die('Unable to connect to');
fwrite($conn, "GET / HTTP/1.0\r\n");
fwrite($conn, "Host:\r\n\r\n");

By doing nothing more than changing the first line to

$conn = @fsockopen('ssl://', 443);

we instruct the PHP streams layer to generate a key pair, request's public key, and transparently apply encryption/decryption to data sent over the stream. That's all it takes!

As of PHP5 we can also create ssl:// server sockets using this easy syntax:

$server = stream_socket_server('ssl://');
while ($conn = stream_socket_accept($server)) {
  /* handle_http_request() is a made up function,
     try writing one of your own! */

The value means "accept connections to any address associated with this server." Although we used port 443 for this example, we're not actually limited to accepting only HTTPs connections. We could listen for LDAPs connections on port 445 or make up our own application layer protocol and listen on whatever port we want to.

Generating a Public Key Certificate and Private Key

Apart from providing SSL extensions to transport-layer protocols, such as TCP, and application-layer protocols, such as HTTP and FTP, the OpenSSL extension also provides a rich API for generating public/private key pairs, preparing Certificate Signing Requests (CSRs), and even producing self-signed certificates.

The first step in using the OpenSSL extension is to create a public/private key pair. We'll do that by calling the openssl_pkey_new() function. When called without arguments, it will use defaults found in your systems openssl.conf file. You may, and possibly should, override these values by passing an array. A full list of options can be found in the online manual (; for now, we'll focus on the most common setting: private_key_bits.

$confargs = array('private_key_bits' => 2048);
$pkey = openssl_pkey_new($configargs);
openssl_pkey_export($pkey, 'private_key.pem');

Next, because OpenSSL expects that you'll be guarding against Man in the Middle attacks (described earlier), we'll need to generate a CSR. openssl_csr_new() expects the $pkey pair along with some identifying information that the key signer will use to confirm your identity.

$info = array(
    'countryName' => 'US',
    'stateOrProvinceName' => 'California',
    'localityName' => 'Placeopolis',
    'organizationName' => 'Thingy Industries',
    'organizationalUnitName' => 'Thingy R&D',
    'commonName' => 'Joe Josephson',
    'emailAddress' => '');
$csr = openssl_csr_new($info, $pkey);
openssl_csr_export($csr, 'cert_request.csr');

countryName should always be the two letter ISO country code. emailAddress should conform to an RFC822 standard email address. The rest of the fields are essentially freeform, but should contain reasonable values that reflect reality.

Now that we have a certificate request in the form of a CSR file, we can send it off to our signing authority of choice. While we wait for their response, we can put our own signature on our public key. This has the effect of saying, "I certify that I am myself and you can trust me because I said so." Not the most resounding testimonial, but while we wait for a response, it will serve our purposes.

$cert = openssl_csr_sign($csr, NULL, $pkey, 365);
openssl_x509_export($cert, 'mycertificate.crt');

This will create a self-signed certificate that will expire one year in the future. Now that we have both a certificate and a private key, let's put it to use.

Encrypting/Decrypting Data

Because public and private keys need not be stored together, one potential use would be to keep the public key on a Web server where data is collected and encrypt it prior to storing it in a database. Then, even if our Web server and database server are compromised, the stolen data and passwords are useless because the attacker does not know our private key.

$pkey = 'file:///etc/public_keys/dbkey.crt';
openssl_public_encrypt($_POST['credit_card'], $cryptnum, $pkey);
$sql = sprintf("INSERT INTO billing (userid, ccnum) values(%d, '%s')",
                    $_SESSION['userid'], addslashes($cryptnum));
database_query($sql) or die('Unable to insert credit card data');

When this script runs, any data posted from the form's credit_card field will be encrypted and added to the database for the user identified by the user ID stored in the currently active session. A third server, perhaps one that is inaccessible from the Internet and therefore not prone to intrusion, can then be responsible for retrieving the data and decrypting it with the matching private key.

The size of the database field needed to store the encrypted credit card number will vary depending on the keysize and the amount of data being encrypted. Assuming we're dealing with a 2,048 bit keysize, the size of an encryption block will be 256 bytes long (2,048 bits at 8 bits per byte).

Data must always be encoded in whole multiples of blocks, so even though a credit card number is typically only 16 bytes long, the data is treated as a whole block (256 bytes) and encrypted as such. Therefore, our database field must be at least char(256).

A more generic formula for determining the resulting size of encrypted data in bytes ($outsize), given an initial data size of $insize bytes for a keysize of $keysize bits, is

$outsize = ceil($insize/($keysize/8))*($keysize/8);

Encrypting and Sending Secure Emails Using S/MIME

Bob's trip to Maui on the company jet was really a corporate espionage mission. After infiltrating the Widget Works, he needs to report back his findings to P. H. Boss, but doesn't want to risk Wally, the Widget Works security guard, discovering that he knows their secrets. The best way for Bob to guard against this is to encrypt his message using P. H. Boss's public key.

$message_headers = array('To:',
                         'Subject: Espionage Summary');
if (openssl_pkcs7_encrypt('espionage_summary.txt', 'espionage_summary.pkcs7',
 file_get_contents('phboss.crt'), $message_headers)) {
       'Espionage Summary',
} else {

When P. H. Boss receives Bob's email, he may need to send back additional instructions. It wouldn't do to have Wally or anyone else at Widget Works intercept those instructions, so he encrypts his response before sending it back. When Bob receives that response, he'll need to decrypt it:

$cert = file_get_contents('bob.crt');
$pkey = file_get_contents('bob.pem');
if (!openssl_pkcs7_decrypt('new_instructions.pkcs7',
                           $cert, $pkey)) {
? >

    Team LiB
    Previous Section Next Section