PHP Encryption / MCrypt Library


Figure 1The ability to encrypt and safeguard data is an essential ability that every serious tech should have in their arsenal. Various technologies can be used to provide crypto solutions with virtually all of the major programming languages.

Technology wise we have the MCrypt Library, hashing functions, SSL, GnuPG, databases typically have built-in crypto functions, hardware devices, smart cards and so on.

Today I have the pleasure of exploring encryption and decryption using the MCrypt Library via PHP's MCrypt API. The goal of this article is to a) demonstrate the MCrypt API in action through a working example; and to b) provide you with a general purpose PHP crypto class I dub cryptastic intended to help you in your pursuit of a decent crypto solution.

Please note, however, this article IS NOT an A to Z solution because it doesn't provide techniques for addressing all of the issues we face such as safely storing and accessing encryption keys which is equally as important as the quality of encryption.

Here is a basic summary of the cryptastic class:
  1. Uses the RIJNDAEL-256 block cipher in CRT mode;
  2. Generates a unique IV for every encryption procedure;
  3. Prepends IV's to ciphertext for easier transmission / storage;
  4. Provides message confidentiality and message integrity via a MAC;
  5. Supports the encryption of PHP variables and arrays;
  6. Ability to generate encryption keys according to the RSA PBKDF2 standard.

Installing MCrypt


MCrypt is a replacement for the old UNIX crypt package / command. In a nut-shell, MCrypt gives us access to a wide range of functions and algorithms that allow us to encrypt and decrypt data, files or data streams.

Before you proceed, you need to ensure MCrypt is installed and ready to go. To get setup, download the files and install everything as instructed on the PHP Mcrypt installation pages. If you're on a Windows system, just follow these instructions:
  1. Download the libmcrypt.dll binary
  2. Place libmcrypt.dll in PHP's installation directory (any directory in the system PATH will do)
  3. Ensure you have php_mcrypt.dll in the PHP extensions directory. If you don't, re-run the PHP installer and enable the MCrypt extension. You can also download the file from pecl4win.
  4. Add extension=php_mcrypt.dll to php.ini
  5. Restart your web server (if you're using one)

Security in General


Security is complex because there are so many avenues for an attacker to bypass it. Cryptography is just one aspect of security. We can use the best crypto solution out there but if it's implemented in an insecure environment we'll merely gain the illusion of security.

There aren't any "one-size-fits-all" security solutions because there are so many diverse environments, all having their own set of issues and obstacles. Using bad technology or poorly implementing good technology can easily lead to a compromise.

There are no perfect or impenetrable solutions. By one means or another, they can probably all be compromised. Each year we hear about sensitive data being compromised at large corporations who spend extraordinary amounts of money hiring security experts to come up with competent solutions. Usually the cause of these compromises is something silly, having little or nothing at all to do with the quality of encryption used.

Our job is to make it as difficult as possible for our data to be compromised by employing sound security at the hardware, operating system, application and network levels. We have to put various obstacles in front of attackers, and the more difficult bypassing each obstacle is and the longer it takes, the better. This gives us time to detect and react to a penetration, start locking the attacker out to prevent further damage or compromise and assess the damage they already caused.

Quick Primer


Before we start looking at code, I'm going to briefly discuss some terminology to help you understand it. I'll try and provide links where appropriate to make it easier for you to explore things in a more in-depth manner.

A cipher is an algorithm (series of procedural steps) for performing encryption and decryption. Through a lot of reading, spending time in the ##crypto channel on freenode and so on I decided on the RIJNDAEL-256 block cipher. This cipher uses a symmetric key algorithm. In other words, the same key is used for both encryption and decryption and is called a secret key. The MCrypt extension supports a number of other ciphers such as DES, BLOWFISH, ENIGMA, SERPENT and TWOFISH, but you'll do well by NOT switching to another cipher.

Traditionally, one-way functions such as MD5 and SHA have been used to encrypt things we don't require the ability to decrypt (like passwords). To verify or authenticate, we accept the original data through some interface, compute the hash and compare it to the hash we stored. If this approach can meet your requirements, please see Encrypting Passwords with PHP Using the RSA PBKDF2 Standard.

In this article we're looking at two-way encryption which affords us the ability to perform decryption and recover the original data. Specifically block ciphers, which require us to specify a mode of operation such as CBC, OFB, CFB, ECB or CTR. Through reading and talking with people in the ##crypto channel I decided upon the Counter (CTR) mode.

Regarding modes, the consensus was to either use CBC or CTR and because CBC requires us to manually perform padding operations I chose CTR, which also has other upsides. Do not use CFB and just as with the cipher, you will do well by NOT switching to another mode. For more information on the chosen cipher and mode of operation, see Cryptographic Right Answers.

Most cipher modes of operation provide message confidentiality but not message integrity which is also important. The code in this article provides message integrity through a MAC (message authentication code). Modes have since been created that offer confidentiality and integrity in one pass, but they may not yet be supported by MCrypt and even if they are you may want to stick with the method used in this article.

With a few exceptions, these block cipher modes of operation require an IV (initialization vector) to produce unique blocks or "streams" independent from other ones that were produced using the same encryption key (the "secret key") without going through a re-keying process. This way, you can encrypt the same data over and over but always arrive at a unique and different ciphertext, which is a term used to describe data in the encrypted or "confidential" state.

As you can see, generating a strong encryption key and keeping it out of the hands of an attacker is just as important as the quality of the actual encryption you use.

Using a password and password salt to derive the actual secret key used to encrypt and decrypt data can help you generate strong keys. The class presented below includes a standardized method to do this for you.

That's Cryptastic!


Alright, now let's take a look at the cryptastic class followed by a few usage examples and then a walkthrough of the code. Cryptastic has the following 3 methods in it: encrypt, decrypt and pbkdf2. The encrypt method performs all of the encryption work and readies the result for transmission / storage; decrypt does the exact opposite of encrypt, readying data for use / display; and the pbkdf2 method generates strong keys derived from a password and a salt, which can be used as an encryption key.

You can download a copy of the code at the very top or bottom of this article.

  1. class cryptastic {
  2.  
  3.     /** Encryption Procedure
  4.      *
  5.      *  @param mixed msg message/data
  6.      *  @param string k encryption key
  7.      *  @param boolean base64 base64 encode result
  8.      *
  9.      *  @return string iv+ciphertext+mac or
  10.      * boolean false on error
  11.     */
  12.     public function encrypt( $msg, $k, $base64 = false ) {
  13.  
  14.         # open cipher module (do not change cipher/mode)
  15.         if ( ! $td = mcrypt_module_open('rijndael-256', '', 'ctr', '') )
  16.             return false;
  17.  
  18.         $msg = serialize($msg);                         # serialize
  19.         $iv = mcrypt_create_iv(32, MCRYPT_RAND);        # create iv
  20.  
  21.         if ( mcrypt_generic_init($td, $k, $iv) !== 0 )  # initialize buffers
  22.             return false;
  23.  
  24.         $msg = mcrypt_generic($td, $msg);               # encrypt
  25.         $msg = $iv . $msg;                              # prepend iv
  26.         $mac = $this->pbkdf2($msg, $k, 1000, 32);       # create mac
  27.         $msg .= $mac;                                   # append mac
  28.  
  29.         mcrypt_generic_deinit($td);                     # clear buffers
  30.         mcrypt_module_close($td);                       # close cipher module
  31.  
  32.         if ( $base64 ) $msg = base64_encode($msg);      # base64 encode?
  33.  
  34.         return $msg;                                    # return iv+ciphertext+mac
  35.     }
  36.  
  37.     /** Decryption Procedure
  38.      *
  39.      *  @param string msg output from encrypt()
  40.      *  @param string k encryption key
  41.      *  @param boolean base64 base64 decode msg
  42.      *
  43.      *  @return string original message/data or
  44.      * boolean false on error
  45.     */
  46.     public function decrypt( $msg, $k, $base64 = false ) {
  47.  
  48.         if ( $base64 ) $msg = base64_decode($msg);          # base64 decode?
  49.  
  50.         # open cipher module (do not change cipher/mode)
  51.         if ( ! $td = mcrypt_module_open('rijndael-256', '', 'ctr', '') )
  52.             return false;
  53.  
  54.         $iv = substr($msg, 0, 32);                          # extract iv
  55.         $mo = strlen($msg) - 32;                            # mac offset
  56.         $em = substr($msg, $mo);                            # extract mac
  57.         $msg = substr($msg, 32, strlen($msg)-64);           # extract ciphertext
  58.         $mac = $this->pbkdf2($iv . $msg, $k, 1000, 32);     # create mac
  59.  
  60.         if ( $em !== $mac )                                 # authenticate mac
  61.             return false;
  62.  
  63.         if ( mcrypt_generic_init($td, $k, $iv) !== 0 )      # initialize buffers
  64.             return false;
  65.  
  66.         $msg = mdecrypt_generic($td, $msg);                 # decrypt
  67.         $msg = unserialize($msg);                           # unserialize
  68.  
  69.         mcrypt_generic_deinit($td);                         # clear buffers
  70.         mcrypt_module_close($td);                           # close cipher module
  71.  
  72.         return $msg;                                        # return original msg
  73.     }
  74.  
  75.     /** PBKDF2 Implementation (as described in RFC 2898);
  76.      *
  77.      *  @param string p password
  78.      *  @param string s salt
  79.      *  @param int c iteration count (use 1000 or higher)
  80.      *  @param int kl derived key length
  81.      *  @param string a hash algorithm
  82.      *
  83.      *  @return string derived key
  84.     */
  85.     public function pbkdf2( $p, $s, $c, $kl, $a = 'sha256' ) {
  86.  
  87.         $hl = strlen(hash($a, null, true)); # Hash length
  88.         $kb = ceil($kl / $hl);              # Key blocks to compute
  89.         $dk = '';                           # Derived key
  90.  
  91.         # Create key
  92.         for ( $block = 1; $block <= $kb; $block ++ ) {
  93.  
  94.             # Initial hash for this block
  95.             $ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
  96.  
  97.             # Perform block iterations
  98.             for ( $i = 1; $i < $c; $i ++ )
  99.  
  100.                 # XOR each iterate
  101.                 $ib ^= ($b = hash_hmac($a, $b, $p, true));
  102.  
  103.             $dk .= $ib; # Append iterated block
  104.         }
  105.  
  106.         # Return derived key of correct length
  107.         return substr($dk, 0, $kl);
  108.     }
  109. }


Using Cryptastic


You can generate an encryption key as follows. Remember, the key you use to encrypt data has to be used to decrypt that data. If you lose the password, salt or for whatever reason aren't able to derive or access the encryption key you'll be unable to recover your data.

Encryption Key

  1. $pass = 'the password';
  2. $salt = 'the password salt';
  3.  
  4. $cryptastic = new cryptastic;
  5. $key = $cryptastic->pbkdf2($pass, $salt, 1000, 32);


The pbkdf2 method expects four parameters: $p (pass), $s (salt), $c (iteration count), $kl (derived key length) and optionally a fifth $algo (algorithm) parameter. You should stick with the default sha256 algorithm, but if for whatever reason you need a hash using something else you have that option.

Use at least 1,000 for $c, and ideally you should use a number much higher such as 20,000 or even 100,000. The integer you supply for $kl depends on the algorithm you'll be using in the encrypt/decrypt methods. A 128 bit cipher requires a 16 octet key, where each octet is 8 bits and 16 * 8 = 128. I'm using a 256 bit cipher, so I have to specify 32 octets, being that 32 * 8 = 256. I'll be returned a 32 character string.

Encrypting / Decrypting

Once you have an encryption key you can start encrypting and decrypting data. To encrypt a message, PHP variable or array, simply pass the encrypt method the message you want to encrypt along with the encryption key. By default, you'll be returned the raw binary ciphertext roughly the same size as the message you pass. You can store this in a database, using an appropriately sized BLOB column.

  1. # secret message
  2. $msg = 'This is the secret message.';
  3.  
  4. # encrypt message
  5. # raw binary ciphertext returned
  6. $encrypted = $cryptastic->encrypt($msg, $key);
  7.  
  8. # decrypt the ciphertext
  9. # original message/variable or array returned
  10. $decrypted = $cryptastic->decrypt($encrypted, $key);
  11.  
  12. // outputs "This is the secret message."
  13. echo $decrypted;


The encrypt method supports an optional third parameter where you can specify the ciphertext be returned base64 encoded (defaults to false). If you send boolean true, you'll be returned a base64 encoded version of the ciphertext that only includes printable characters, which may prove useful under certain circumstances. However, please note that the base64 output will be notably larger than the original message you pass in.

  1. # secret message
  2. $msg = array('message' => 'This is the secret message.');
  3.  
  4. # encrypt message
  5. # base64 encoded ciphertext returned
  6. $encrypted = $cryptastic->encrypt($msg, $key, true);
  7.  
  8. # decrypt the ciphertext
  9. # original message/variable or array returned
  10. $decrypted = $cryptastic->decrypt($encrypted, $key, true);
  11.  
  12. // outputs "This is the secret message."
  13. echo $decrypted['message'];


Cryptastic Walkthrough


If you found the cryptastic class a bit daunting at first glance rest easy, a little explanation will go a long way. Let's start by walking through the encrypt method, followed by decrypt and finally the pbkdf2 method.

encrypt()

The first thing this method does is load the module of the specified cipher algorithm (rijndael-256) and mode (CRT) via the mcrypt_module_open call and set the descriptor in $td which is required by other functions. I set the second and fourth parameters (algorithm directory / mode_directory) to empty strings, which means they'll default to the values set in mcrypt.algorithms_dir and mcrypt.modes_dir PHP INI directives or if those aren't set the default directories compiled into libmcrypt.

If an error occurs opening the module, boolean false is returned.

  1. if ( ! $td = mcrypt_module_open('rijndael-256', '', 'ctr', '') )
  2.     return false;


Next, $msg is run through serialize to support PHP variables and arrays.

  1. $msg = serialize($msg);


After serializing, a unique IV of the appropriate length is created. The first parameter I specify being 32, which is the IV size needed for the 256 bit cipher I'm using. This code was specifically written for the cipher I'm using. If you changed ciphers, you could use the mcrypt_get_iv_size function to determine the IV size it requires.

  1. $iv = mcrypt_create_iv(32, MCRYPT_RAND);


Next, the buffers are initialized. If an error occurs, boolean false is returned.

  1. if ( mcrypt_generic_init($td, $k, $iv) !== 0 )
  2.     return false;


After initializing the buffers, the actual encryption is performed via mcrypt_generic.

  1. $msg = mcrypt_generic($td, $msg);


Now the IV is prepended to the ciphertext for easy transmission / storage. After that, a message authentication code is calculated against the iv+ciphertext using the pbkdf2 method. Ideally, you should use a salt specifically for calculating MAC's but using the encryption key as the salt like I'm doing here shouldn't be any problem. The resulting MAC is then appended. So, what you actually transmit or store is $msg = iv+ciphertext+mac.

  1. $msg = $iv . $msg;                          # prepend iv
  2. $mac = $this->pbkdf2($msg, $k, 1000, 32);   # create mac
  3. $msg .= $mac;                               # append mac


Then we clear the buffers and close the cipher module.

  1. mcrypt_generic_deinit($td); # clear buffers
  2. mcrypt_module_close($td);   # close cipher module


If you specified boolean true as the $base64 parameter, $msg will be returned as a base64 encoded string, otherwise the raw binary result will be returned.

  1. if ( $base64 ) $msg = base64_encode($msg);
  2.  
  3. return $msg;


decrypt()

The decrypt method is more or less the exact opposite of encrypt. If $base64 is true, the first thing it does is decode the $msg you sent it.

  1. if ( $base64 ) $msg = base64_decode($msg);


Next, the cipher module is opened in the same manner it was in the encrypt method. After that, it extracts the IV, determines the MAC offset and extracts it and then extracts the actual ciphertext.

  1. if ( ! $td = mcrypt_module_open('rijndael-256', '', 'ctr', '') )
  2.     return false;
  3.  
  4. $iv = substr($msg, 0, 32);                      # extract iv
  5. $mo = strlen($msg) - 32;                        # mac offset
  6. $em = substr($msg, $mo);                        # extract mac
  7. $msg = substr($msg, 32, strlen($msg)-64);       # extract ciphertext


Before any actual decryption occurs, a MAC is calculated against the iv+ciphertext that was extracted and then compared to the MAC that was extracted to determine if any tampering occurred. If the MAC's don't match, boolean false is returned.

  1. $mac = $this->pbkdf2($iv . $msg, $k, 1000, 32);     # create mac
  2.  
  3. if ( $em !== $mac ) # authenticate mac
  4.     return false;


Finally, the buffers are initialized, the ciphertext is decrypted, the message is run through unserialize to support variables and arrays, the buffers are cleared, cipher module is closed and the original message or data is returned.

  1. if ( mcrypt_generic_init($td, $k, $iv) !== 0 )  # initialize buffers
  2.     return false;
  3.  
  4. $msg = mdecrypt_generic($td, $msg);             # decrypt
  5. $msg = unserialize($msg);                       # unserialize
  6.  
  7. mcrypt_generic_deinit($td);                     # clear buffers
  8. mcrypt_module_close($td);                       # close cipher module
  9.  
  10. return $msg;                                    # return original msg


pbkdf2()

PBKDF2 stands for Password-based Key Derivation Function, version 2. This is a PHP-based implementation of the RSA Laboratories standard, specifically PKCS #5 v2.0 (RFC 2898). To learn how this method works, please see Encrypting Passwords with PHP Using the RSA PBKDF2 Standard.