Recently we released the new Zend\Crypt component in the 2.0.0beta4 version of the Zend Framework project (the release notes of beta4 can be found here). The aim of this component is to offer cryptographic tools for security needs, like encryption and decryption of sensitive data, how to safely store of user�s passwords, generation of cryptographic keys based on user's password, etc.
Zend\Crypt contains the following components:
- Zend\Crypt\Password, for the management of passwords (secure storage, compatibility with other systems);
- Zend\Crypt\Key\Derivation, for the generation of cruptographic keys based on a user's passwords;
- Zend\Crypt\Symmetric, implementation of symmetric ciphers (based on the Mcrypt extension);
- Zend\Crypt\PublicKey, implementation of public key ciphers like the RSA and the Diffie-Hellman key exchange algorithm (based on the OpenSSL extension);
- Zend\Crypt\BlockCipher, a friendly encryption/decryption schema using a block cipher in CBC mode + HMAC authentication;
- Zend\Crypt\Hash, implementation of the hash algorihtms available in the Hash extension (included in the standard distribution of PHP 5.3);
- Zend\Crypt\Hmac, implementation of the HMAC algorithms available in the Mhash extension (included in the standard distribution of PHP 5.3).
Moreover the ZF 2.0.0beta4 includes a new Zend\Math namespace shipped with a more robust pseudo-random number generator.
In this post I would like to present some of the new capabilities of the Zend\Crypt component. Let's start with the main course: how to encrypt and decrypt data using strong cryptography standards.
Encrypt and decrypt data
In order to simplify the usage of cryptography in PHP we released the Zend\Crypt\BlockCipher class that can be used to encrypt and decrypt sensitive data (string) with a very simple API. This class is able to encrypt a string using a block cipher providing also authentication using the HMAC algorithm. The encryption+authentication schema is implemented using the encrypt-then-authenticate methodology. The default encryption mode is CBC and the default HMAC algorithm is SHA256. We know that there are special Authenticated Encryption algorithms like OCB, EAX, CCM, GCM that can guarantee a better security but for general use cases CBC+HMAC is considered secure. Anyway, in the future, we would like to add the support of such advanced algorithms in Zend Framework.
The usage of Zend\Crypt\BlockCipher is very simple, here an example:
use Zend\Crypt\BlockCipher;
$cipher = BlockCipher::factory('mcrypt', array('algorithm' => 'aes'));
$cipher->setKey('this is the encryption key');
$text = 'This is the message to encrypt';
$encrypted = $cipher->encrypt($text);
printf("Encrypted text: %sn", $encrypted);
We used the AES encryption algorithm implemented by the mcrypt adapter (Zend\Crypt\Symmetric\Mcrypt). The Zend\Crypt\BlockCipher try to use always the maximum key size for the encryption algorithm (in the case of AES that size is 256 bits). The encryption key is generated using the PBKDF2 algorithm starting from the user's key. PBKDF2 is also used to generate the authenticatin key used by HMAC. The default padding schema for the block cipher is PKCS7 (RFC 5652). The output of the previous snippest code is something like that:
Encrypted text: c093e6d72510c6ac14a075638b7ee6725abd599413a3562aaa51b9e71c941045kHwdIDRC7kOZeKGKTib115X5rMN/VM0vYQ13HkyPUaSo0S9reCUEpyp+T3oW9+wh
The encrypted text is encoded in Base64. The output is the concatenation of the HMAC value, followed by the random Initialization Vector (IV) and finally by the encrypted text. Because we use a random IV we guarantee that each output is different, even using the same encryption key and the same plaintext.
The array passed to the mcrypt adapter, using the factory, is the array of options. You can specify the following parameters:
- algorithm (or algo), the name of the block cipher to use (the supported algorithms are: aes (rijndael-128), rijndael-192, rijndael-256, blowfish, twofish, des, 3des, cast-128, cast-256, saferplus, serpent);
- mode, the encryption mode of the block cipher (the supported modes are: cbc, cfb, ctr, ofb, nofb, ncfb);
- key, the encryption key;
- IV (or salt), the Initialization Vector (IV) also known as salt;
- padding, the padding mode (right now we support only the PKCS7 standard);
The default values are: AES cipher, random IV, PKCS7 padding.
In order to decrypt a ciphertext we can use the decrypt() method of BlockCipher, here an example:
use Zend\Crypt\BlockCipher;
$cipher = BlockCipher::factory('mcrypt', array('algorithm' => 'aes'));
$cipher->setKey('this is the encryption key');
$ciphertext = 'c093e6d72510c6ac14a075638b7ee6725abd599413a3562aaa51b9e71c941045kHwdIDRC7kOZeKGKTib115X5rMN/VM0vYQ13HkyPUaSo0S9reCUEpyp+T3oW9+wh';
$encrypted = $cipher->decrypt($text);
printf("Decrypted text: %sn", $encrypted);
The output of the previous example should be "Decrypted text: This is the message to encrypt".
Store a user's password
In the past we usually stored user's password using the MD5 value of the password + a random salt (for instance, see my post of some years ago). Unfortunately, this technique is not secure anymore because the modern CPU are very fast and they are able to provide dictionary attacks in a small amount of time (think also to the power of cloud computing, read this article How To Safely Store A Password for more info). The best alternative is to use a special algorithm like bcrypt, that can guarantee a secure hash value of a user's password. Bcrypt is considered secure because it's very slow, the computational time of a single hash can be of some seconds. That means a brute force or a dictionary attack needs huge amount of time to be completed.
The bcrypt algorithm is implemented by the Zend\Crypt\Password\Bcrypt class (using the crypt() function of PHP). The usage is very simple, here an example:
use Zend\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt();
$password = $bcrypt->create('password');
printf ("Password: %sn", $password);
The output of the create() method of Bcrypt is a string of 60 characters, like that:
$2a$14$yuD/3v/IdbdOZ0pfIjUyJ.a0Q4Ue0UTAoES2BIgK0Op1Z6IF9.aTS
If we try to execute the example code two times we will obtain different outputs. That because if we don't provide a salt value the Bcrypt class will generate a random salt each time. If you want to assign a salt value you can use the setSalt() method (the salt must be a string of at least 16 bytes).
The bcrypt algorithm needs a special cost parameter. This parameter specify the amount of work (cycles) to do in order to elaborate the hash value. More cycles means more computational time and consequently more security. The cost parameter is an integer value from 4 to 31. The default value is 14, that is equivalent to 1 second of computation using an Intel Core i5 CPU at 3.3 Ghz. You can specify a different cost value using the setCost() method, here an example:
use Zend\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt();
$bcrypt->setSalt(15);
$start = microtime(true);
$password = $bcrypt->create('password');
$end = microtime(true);
printf ("Password : %sn", $password);
printf ("Exec. time: %.2fn", $end-$start);
Try to execute this snippet code on your server and you will see how many seconds you need to get the hash value. For a security reason, I suggest to use a cost value that guarantee at least 1 second of computation.
The salt and cost parameters can also be specified, using an array, during the constructor of Zend\Crypt\Password\Bcrypt, as follow:
use Zend\Crypt\Password\Bcrypt;
$bcrypt = new Bcrypt(array(
'salt' => '1234567890123456',
'cost' => 16
));
If we want to check if a password is valid against an hash value we can use the verify($password, $hash) method, where $password is the value to check and $hash is the hash value generated using bcrypt. This method returns true if the password is valid and false otherwise.
Generate a cryptography key from a user's password
You should never use a user's password as cryptographic key, for instance to encrypt some data, this is a well known security principle. User's passwords are bad for two reasons:
- they are not random, at all;
- they generate a small space of keys (low entropy).
For the first reason, think about your personal passwords and you will easy understand why they are not random. For the second reason, imagine that we have a user's password of 8 characters, the key space should be, in theory, 256^8 (because each ASCII character has 256 possibile values) but if you use only letters, numbers and a couple of other symbols we have a space key of about 65^8, less than a quarter of the previous one (in practice, the key space is even smaller because they are not random).
That said, if you want to use a user's password, in your cryptogtaphic schema, you should always use a key derivation function (or KDF). KDF are special algorithms that generate cryptographic keys, of any size, from a user's password. One of the most used KDF is the PBKDF2 algorithm (specified in RFC 2898).
PBKDF2 applies a pseudorandom function, such as a cryptographic hash, cipher, or HMAC to the input password or passphrase along with a salt value and repeats the process many times to produce a derived key, which can then be used as a cryptographic key in subsequent operations. The added computational work makes password cracking much more difficult, and is known as key stretching.
The PBKDF2 is implemented in Zend\Crypt\Key\Derivation\Pbkdf2. The usage is very simple, here an example:
use Zend\Crypt\Key\Derivation\Pbkdf2,
Zend\Math\Math;
$salt = Math::randBytes(32);
$pass = 'this is the password of the user';
$hash = Pbkdf2::calc('sha256', $pass, $salt, 10000, 32);
The first parameter of the Pbkdf2 is the hash algorithm to use, sha256 in our example, the second parameter is the user's password, the third parameter is the salt value, that in our example is a random set of bytes (generated using Zend\Math\Math), the fourth parameter is the number of iterations to use in the algorithm and the last parameter is the size of key to be generated, in bytes.
Regarding the number of iterations, this parameter is related with the CPU speed. It's not so easy to identify the minimum number of iteration, that can guarantee a good security, for the Pbkdf2 algorithm (you can read this interesting discussion on stackexchange). We used a value of 10'000 but this number must be evaluated on your system. For instance, if you want to have a computation time of about 1 second using an Intel Core i5 CPU at 3.3Ghz you need at least 100'000 iterations.
The output of the Pbkdf2::calc() function is in binary format.
Random number generator
In the beta4 of ZF2 we have refactored the Zend\Crypt\Math component in the new Zend\Math component. That because we are going to support additional mathematical functions in the future of ZF2 that are not related to cryptography. We added a couple of static methods in the Zend\Math\Math class that can be used to generated a random set of bytes or random numbers. These functions are:
- Zend\Math\Math::randBytes($length, $strong = false)
- Zend\Math\Math::rand($min, $max, $strong = false)
The first method randBytes() can be used to generate a random set of bytes with size $length. The optional parameter $strong indicates that we want use a strong random number generator, for cryptographic purposes. This feature is provided using the openssl_random_pseudo_bytes() function of the OpenSSL extension. If the OpenSSL extension is not installed it tries to use the the mcrypt_create_iv() function of the Mcrypt extension. And finally, if Mcrypt is not installed it uses the mt_rand() function of PHP (that is not secure for cryptographic purposes). If $strong is set to true and you try to generate random values in a PHP environment without the OpenSSL and the Mcrypt extensions you will get an Exception.
The second method rand() is able to generate a random integer value between the range $min, $max.
Future works
We are going to extend the features of Zend\Crypt and in particular adding more key derivation algorithms (we just commited the SaltedS2k in the ZF2 github repository), more padding methods for the block ciphers, more password algorithms (we would like to offer adapters for specific systems) and a better support for the public key encryption algorithms.
Moreover, we have already some proposals in place to improve the random number generator of Zend\Math using the RFC 4086 recommendations (for more information you can read here the proposal of Denis Portnov).
We hope that the new Zend\Crypt class of Zend Framework 2 will help developers to use, or start to use, cryptography in their PHP projects.
Thanks
I would like to thanks Anthony Ferrara, the author of the PHP-CryptLib project for the inspiration that his project gave me, during the implementation of Zend\Crypt. The PHP-CryptLib is an awesome project for the implementation of cryptographic algorithms in pure PHP. The Zend\Crypt don't want to be an alternative to PHP-CryptLib, the scopes are different. Zend\Crypt offers a basic crypto component for ZF2 users. PHP-CryptLib is a full cryptography libraries for advanced use cases.