Encrypting credit card information with OpenSSL

Recently, when I researched the encryption of credit card information, or card holder data (CHD) as it’s known in the industry, I was somewhat frightened by what I found. Almost every tutorial suggested using MySQL’s AES functions. While AES is an excellent symmetric-key algorithm, a symmetric-key algorithm is not a great choice for storing CHD long term. Symmetric-key algorithms use a single key for encryption and decryption. So in order to encrypt a user’s submitted CHD, the key must be accessible by the code. The code might look something like this:

$key = '[x[{n&)NF11\p506J`>~ta;~8Xq^1Y>4n5`.YoDfy]>1q%tB?J8pR&5CV9';
$sql = "INSERT INTO credit_cards (name, number, expiry)
    VALUES (AES_ENCRYPT('$name', '$key'), AES_ENCRYPT('$number', '$key'), AES_ENCRYPT('$expiry', '$key')";

Clearly it is bad security to have the key capable of decrypting the CHD sitting on the server. If an attacker gains access to the server, they will have access to both the database and the key to decrypt the CHD.

A better option is to use a public-key encryption algorithm. Public-key encryption uses two keys: a public key and a private key. The private key may also have a password, required to unlock it for use. For our implementation, we will store the public key in the code on the server to encrypt the CHD as its submitted. Once encrypted with the public key, the CHD can only be decrypted with the private key. We will keep this private key in a safe place (offline) and when it comes time to decrypt the CHD (to process a bill for example) we will enter the private key and its password into a web form and submit it over SSL. If an attacker gains access to the server they will only have access to the public key (which again, can only be used for encryption) and will not be able to decrypt the CHD. Obviously this implementation only works for you if you don’t mind manually processing payments. You could however store the private key on a secure server (on a different network, behind a firewall) that would run a program to automatically process your payments.

The open source software package OpenSSL is excellent for implementing this public-key encryption technique. Many programming languages have an OpenSSL library including PHP (you will need to have compiled with –with-openssl). To get started with OpenSSL, you first need to generate your public and private keys and choose a private key password:

openssl genrsa -des3 -out private_key.pem 2056
openssl rsa -pubout -in private_key.pem -out public_key.pem

In PHP, you can try the following script to get started (again, make sure PHP is compiled –with-openssl):

$num = '4111111111111';
$name = 'Brad Touesnard';

$public_key = openssl_pkey_get_public('file://public_key.pem');
$private_key = openssl_pkey_get_private('file://private_key.pem', 'your_password');

echo 'Original: ' . $name . ' - ' . $num . "\n";

openssl_public_encrypt($name, $name_enc, $public_key);
openssl_public_encrypt($num, $num_enc, $public_key);

echo 'Encrypted: ' . $name_enc . ' - ' . $num_enc . "\n";

openssl_private_decrypt($name_enc, $name_dec, $private_key);
openssl_private_decrypt($num_enc, $num_dec, $private_key);

echo 'Decrypted: ' . $name_dec . ' - ' . $num_dec . "\n";