In a book about security, cryptography is an expected topic. I have chosen to neglect cryptography in the majority of the book because its purpose is narrow, and developers need to pay attention to the big picture. Relying on encryption is often a red herring. It serves its purpose well, but encrypting something doesn’t magically make an application secure.
The key types of cryptography with which a PHP developer should be familiar are as follows:
Symmetric cryptography
Asymmetric (public key) cryptography
Cryptographic hash functions (message digests)
Message authentication codes (MACs)
The majority of this appendix focuses on symmetric cryptography
using the mcrypt extension. Other good resources that you should review are as follows:
Applied Cryptography, by Bruce Schneier (Wiley)
You should never store cleartext passwords in a database. Instead, store the hash of the password, and use a salt for best results:
<?php
/* $password contains the password. */
$salt = 'SHIFLETT';
$password_hash = md5($salt . md5($password . $salt));
/* Store password hash. */
?>When you want to determine whether a user has provided the correct password, hash the provided password using the same technique, and compare the hashes:
<?php
$salt = 'SHIFLETT';
$password_hash = md5($salt . md5($_POST['password'] . $salt));
/* Compare password hashes. */
?>If the hashes are identical, you are reasonably assured that the passwords are also identical.
Using this technique, it is not possible to remind users what their passwords are. When a user forgets her password, you instead let her create a new one, and you store the hash of the new password in the database. Of course, you want to be very careful to identify the user correctly—password-reminder mechanisms are frequent targets of attack and a common source of security vulnerabilities.
The standard PHP extension for cryptography is mcrypt, and it supports a number of different cryptographic algorithms. To see which ones are supported on your platform, use the mcrypt_list_algorithms() function:
<?php
echo '<pre>' . print_r(mcrypt_list_algorithms(), TRUE) . '</pre>';
?>Encrypting and decrypting data are achieved by using mcrypt_encrypt() and mcrypt_decrypt(), respectively. Each of these functions accepts five arguments, the first of which is the algorithm to use:
<?php
mcrypt_encrypt($algorithm,
$key,
$cleartext,
$mode,
$iv);
mcrypt_decrypt($algorithm,
$key,
$ciphertext,
$mode,
$iv);
?>The key (second argument) is extremely sensitive, so you want to be sure to keep this in a safe place. The technique described in Chapter 8 for protecting your database access credentials can be used to protect the key. A hardware key provides superior security, and this is the best choice for those who can afford it.
There are numerous modes that you can use, and you can use mcrypt_list_modes() to view a list of available modes:
<?php
echo '<pre>' . print_r(mcrypt_list_modes(), TRUE) . '</pre>';
?>The fifth argument ($iv) is the initialization vector, and it is created with the mcrypt_create_iv() function.
The following is an example class that offers basic methods for encrypting and decrypting:
class crypt
{
private $algorithm;
private $mode;
private $random_source;
public $cleartext;
public $ciphertext;
public $iv;
public function __construct($algorithm = MCRYPT_BLOWFISH,
$mode = MCRYPT_MODE_CBC,
$random_source = MCRYPT_DEV_URANDOM)
{
$this->algorithm = $algorithm;
$this->mode = $mode;
$this->random_source = $random_source;
}
public function generate_iv()
{
$this->iv = mcrypt_create_iv(mcrypt_get_iv_size($this->algorithm,
$this->mode), $this->random_source);
}
public function encrypt()
{
$this->ciphertext = mcrypt_encrypt($this->algorithm,
$_SERVER['CRYPT_KEY'], $this->cleartext, $this->mode, $this->iv);
}
public function decrypt()
{
$this->cleartext = mcrypt_decrypt($this->algorithm,
$_SERVER['CRYPT_KEY'], $this->ciphertext, $this->mode, $this->iv);
}
}
?>This class is referenced in other examples; the following example demonstrates its use:
<?php
$crypt = new crypt();
$crypt->cleartext = 'This is a string';
$crypt->generate_iv();
$crypt->encrypt();
$ciphertext = base64_encode($crypt->ciphertext);
$iv = base64_encode($crypt->iv);
unset($crypt);
/* Store $ciphertext and $iv (initialization vector). */
$ciphertext = base64_decode($ciphertext);
$iv = base64_decode($iv);
$crypt = new crypt();
$crypt->iv = $iv;
$crypt->ciphertext = $ciphertext;
$crypt->decrypt();
$cleartext = $crypt->cleartext;
?>This extension requires you to compile PHP with the —with-mcrypt flag. See http://php.net/mcrypt for requirements and installation instructions.
One of the most frequent questions I am asked is how to store credit card numbers securely. My first instinct is always to inquire whether it is absolutely necessary to store them. After all, regardless of implementation, taking unnecessary risks is never wise. There are also strict laws governing the processing of credit card information, and I am always careful to note that I am not a legal expert.
Rather than discussing methods uniquely related to credit card processing, I have chosen to demonstrate how to store encrypted data in the database and decrypt it upon retrieval. This approach incurs a performance penalty, but it does offer an extra layer of protection. The primary advantage is that a compromised database doesn’t necessarily expose the encrypted data, but this is true only if the key is kept secret. Therefore, the secrecy of the key is as important as the implementation itself.
To store encrypted data in the database, first encrypt the data, then concatenate the initialization vector and the ciphertext together to create a string to store in the database. Because this is a binary string, use base64_encode() to convert it to a string that is safe to treat as plain text:
<?php
$crypt = new crypt();
$crypt->cleartext = '1234567890123456';
$crypt->generate_iv();
$crypt->encrypt();
$ciphertext = $crypt->ciphertext;
$iv = $crypt->iv;
$string = base64_encode($iv . $ciphertext);
?>Store this string in the database. Upon retrieval, reverse this process as follows:
<?php
$string = base64_decode($string);
$iv_size = mcrypt_get_iv_size($algorithm, $mode);
$ciphertext = substr($string, $iv_size);
$iv = substr($string, 0, $iv_size);
$crypt = new crypt();
$crypt->iv = $iv;
$crypt->ciphertext = $ciphertext;
$crypt->decrypt();
$cleartext = $crypt->cleartext;
?>If the security of your database is in question, or if the data that you store in sessions is particularly sensitive, you might wish to encrypt all session data. I do not recommend this approach unless absolutely necessary, but if you feel that your situation warrants it, this section provides an example implementation.
The idea is pretty simple. In fact, in Chapter 8, you are shown how to implement your own session mechanism by calling session_set_save_handler(). With a minor adjustment to the functions that store and retrieve data, you can encrypt data that you store in the database and decrypt the data that you retrieve:
<?php
function _read($id)
{
global $_sess_db;
$algorithm = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_CBC;
$id = mysql_real_escape_string($id);
$sql = "SELECT data
FROM sessions
WHERE id = '$id'";
if ($result = mysql_query($sql, $_sess_db))
{
$record = mysql_fetch_assoc($result);
$data = base64_decode($record['data']);
$iv_size = mcrypt_get_iv_size($algorithm, $mode);
$ciphertext = substr($data, $iv_size);
$iv = substr($data, 0, $iv_size);
$crypt = new crypt();
$crypt->iv = $iv;
$crypt->ciphertext = $ciphertext;
$crypt->decrypt();
return $crypt->cleartext;
}
return '';
}
function _write($id, $data)
{
global $_sess_db;
$access = time();
$crypt = new crypt();
$crypt->cleartext = $data;
$crypt->generate_iv();
$crypt->encrypt();
$ciphertext = $crypt->ciphertext;
$iv = $crypt->iv;
$data = base64_encode($iv . $ciphertext);
$id = mysql_real_escape_string($id);
$access = mysql_real_escape_string($access);
$data = mysql_real_escape_string($data);
$sql = "REPLACE
INTO sessions
VALUES ('$id', '$access', '$data')";
return mysql_query($sql, $_sess_db);
}