Strong cryptography in PHP


How to use cryptography in PHP Posted by on January 02, 2011

Update: I edited the article on Jan 2017 with some additional information about PHP 7 and other security considerations.

If you are a professional web developer, security is an important aspect of your job. If you are planning to store some critical or sensitive data in your web application, like passwords, credit cards, etc, you should use strong cryptography.

What is strong cryptography?

Strong cryptography is the usage of systems or components that are considered highly resistant to cryptanalysis, the study of methods to cracking the codes.

Theoretically speaking, if we encrypt and store sensitive data in a database, file, or whatever, a malicious attacker will not be able to decrypt it without the knowledge of the key.

How can we proof that an attacker will not be able to decrypt the data? We cannot. We can only trust in the security of well known algorithms.

Even a well known algorithm can become non-secure, for instance DES, a FIST standard in 1976 is not secure since 1998, when the Electronic Frontier Foundation (EFF) built a machine, the EFF DES cracker, to perform a brute force search of DES cipher's key space. This machine was able to find the key of an encrypted message in 56 hours.

Today, thanks to GPU power we can decrypt DES in seconds! That means is very important to be updated with the security news of encryption algorithm.

Why we should use standard algorithm?

The answer is simple, because build an encryption algorithm or protocol is a very complicated and difficult stuff. You need to be an expert, you must submit the algorithm to the security community for review, you need to wait some years for feedbacks and at the end you have a good chance to fail.

There are many examples of new cryptographic protocols, even developed by expert, that has been attacked in the past years. For instance, the protocol MTProto developed by Telegram. This new cryptographic protocol has been attacked in 2015 after a code review, and this paper demonstrated that is not IND-CCA secure, since it is possible to turn any ciphertext into a different ciphertext that decrypts to the same message.

Most people think that secrecy equals security, and open-source software doesn't sound compatible with the idea of strong cybersecurity. This is false! Bruce Schneier, a famous security expert and author of many encryption algorithms, wrote several times about the value of open source cryptography.

Strong cryptography in PHP

PHP offers different implementations of the most important cryptographic algorithms. In particular PHP has the following cryptographic extensions:

  • Hash
  • mcrypt
  • OpenSSL

The Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2. This extension replace the old mhash extension. With this extension you can generate hash values or HMAC (Hash-based Message Authentication Code). These extension support the most common hash algorithms used in strong cryptography. If you want to know which algorithms are supported by your PHP environment you can use the function hash_algos() that gives a list of all the algorithms supported.

The mcrypt extension has been deprecated in PHP 7.1. I recommend to do not use it anymore, move to OpenSSL as soon as possible.

The OpenSSL extension uses the functions of the OpenSSL project for generation and verification of signatures and for sealing (encrypting) and opening (decrypting) data. You can use OpenSSL to protect data using symmetric encryption (also authenticated encryption) public key cryptography with the RSA algorithm.

Best practices in PHP

Below, I reported 10 best practices to follow when using cryptography in PHP:

1) Use standard algorithms

Always use a standard algorithm to encrypt your data. Never use an homemade encryption algorithm, never!

PHP offers many strandard algorithm, mainly using OpenSSL. For instance:

  • Simmetric-key algorithm: AES, a FIST 197 standard since 2001;
  • Public-key algorithm: RSA, an industry standard algorithm used in many products;
  • Hash function: SHA, in particular SHA-256 or SHA-512. Don't use SHA-1 for cryptography!
  • Key derivation algorithm: PBKDF2, is a very popular algorithm (RFC 2898).

2) Key space

The key space is a very important parameter for the security of a cipher. If no explicit design strength is give by a cipher, the design strength equals to the key size. For instance, the DES cipher uses 56-bit key, that means the key space is 2^56 equal to 72,057,594,037,927,936, more than 72 quadrillion. This numbers seems to be big enough but it's not for modern computers, EFF proved it since 1998.

For symmetric ciphers, even if 128 bit sounds reasonable I would suggest to move to 256 bit (here an interesting article).

Regarding public-key cryptography, the most used size in industry is 2048 bit and this is considered secure for the next two decades, with fair (but not absolute) confidence.

3) Kerchoof's principle

Auguste Kerckhoffs was a Dutch linguist and cryptographer who was professor of languages at the School of Higher Commercial Studies in Paris in the late 19th century. He wrote, in a famous article of "le Journal des Sciences Militaires", the following sentence, that is considered a must in the modern cryptography:

A cryptosystem should be secure even if everything about the system, except the key, is public knowledge

There's also a similar quote by Claude Shannon, the father of information theory:

The enemy knows the system

In my opinion, you can have security only with the usage of open source algorithms. If the source code has been tested by people around the world the probability to find a bug (and a fix) is higher using open source software compared to closed source.

4) Don't use rand() or mt_rand()

The PHP functions rand() and mt_rand() don't generate cryptographically secure pseudo-random values.

The rand() function uses the libc library to generate pseudo-random numbers that is not secure for cryptography applications. It generates random numbers using a linear additive feedback method, with a short period, that is predictable.

Even the mt_rand() function is not secure from a cryptographically point of view. It uses the Mersenne Twister algorithm to generate pseudo random numbers. This function is better than the rand() because it faster and it produces pseudo random numbers with a biggest period but is still a deterministic algorithm so is predictable.

To generate a cryptographically secure random number with PHP 7 we can use random_int() or random_bytes() for binary string.

If you are using PHP 5.x you can use paragonie/random_compat library that is a polyfill for the PHP 7 random functions.

5) Use bcrypt to store a password

If you are using MD5 or SHA functions to store user's passwords, please don't do that! Even if you salt the hash you are not safe. A random salt can protect from a dictionary attack but it cannot prevent brute-force attacks. Using a GPU (also a cheap one) you can decrypt passwords in seconds.

A secure way to store a password is to use bcrypt algorithm. This one-way function can prevent brute-force attacks because is computationally slow. If someone want to attack the algorithm needs a lot of time (years) to generate all the values.

From PHP 5.5+ we can use two special functions for the bcrypt algorithm, they are password_hash() and password_verify()

Here is reported an example for generate the hash value of a user's password:

$password='supersecretpassword';
$hash = password_hash($password, PASSWORD_BCRYPT);
echo $hash;

This script will print something like:

$2y$10$TZp7a29gDmtwa5Inch0Eq.INxx1tnjY9k6gWxwH/TUoX4uJYTxm76

The output of password_hash is a string of 60 bytes, with an header of 7 bytes including the bcrypt specification ($2y$) and the cost parameter (10$). Each time you generate an hash you will get a different output. This because the algorithm uses a random salt each time, to improve the security.

To verify an hash value, with a given password, we can use password_verify():

if (password_verify($password, $hash)) {
  echo 'The password is valid';
} else {
  echo 'Invalid password';
}

If you are using an old version of PHP, you can use ircmaxell/password_compat library that implements the password_hash() and password_verify() functions.

6) Use authenticated encryption

Encryption is not enough to protect data, you need also integrity and authenticity. Without integrity, you can alter an encrypted data without any evidence of it. Without authentication, you cannot be sure that the data are generated by a legitimate user.

We can add authentication to an encryption system using encrypt-then-authenticate approach or we can use authenticated encryption that offers authentication built-in in the algorithm.

For the encrypt-then-authenticate approach you can use a PHP library like zendframework/zend-crypt that offers authentication using HMAC-SHA256.

Starting from PHP 7.1 you can use authenticated encryption with OpenSSL. In particular, we can use AES with 256 bit in GCM or CCM mode (aes-256-gcm and aes-256-gcm). I wrote a specific post that show how to use it in PHP.

7) Force the usage of robust password

Using small and simple user's password is bad, we know. Today (2017), you should prevent a user to choose a password less than 12 characters, if permitted. I suggest to use a pass phrases instead of some "random" password. A pass phrase is easy to remember than passwords and more secure against brute forcing, because of their length.

In PHP, you can use the CrackLib library to test the "strength" of a password.

8) Don't use password as encryption key

Never use a user's password as encryption key! A user's password is not random and it doesn't have a good entropy bits. Use always a Key Derivation Function (KDF) to generate an encryption key starting from a user's password.

One of the most used key derivation function is the PBKDF2 algorithm. PHP 5.5+ offers hash_pbkdf2() function for that.

Here is reported an example:

$password = 'supersecretpassword';
$salt = random_bytes(16);
$hash = hash_pbkdf2("sha256", $password, $salt, 20000);
var_dump($hash);

In this example, the hash_pbkdf2() function generates the hash values iterating SHA-256 for 20,000 times. The function uses also a random salt value that is very important for the security of the algorithm. We used the random_bytes() function of PHP 7. If you are using PHP 5 you can use the paragonie/random_compat library.

The output of the PBKDF2 is a string of 32 bytes in hex format (64 characters). If you want a binary string with a different size you have to specify two additional parameters. For instance, if you need an hash value of 128 bytes in binary format you can use the following syntax:

$hash = hash_pbkdf2("sha256", $password, $salt, 20000, 128, true);

Another important parameter of the PBKDF2 algorithm is the number of rounds. I used the value 20,000 that is used in many applications but you should use the maximum number of rounds which is tolerable, performance-wise, in your application. Here you can have more information on the number of rounds.

9) Use Base64 to encode data

If you need to exchange encrypted data with different systems, for instance, trasmitting data over internet, is reccomended to encode the data in Base64.

In PHP you can use the functions base64_encode() and base64_decode(). This encoding will garantee that your data will be stored correctly independently of the encoding system used in your environment.

10) Update your PHP version

The last but not least best practice is to update your PHP version. Work with the latest PHP version if you can, this is very important for security reason.

It's important to note the current supported versions of PHP. Today (Jan 2017), the only supported are PHP 5.6 (only security fixes), 7.0 and 7.1. If you are using an old PHP version, you are exposing your applications to potential security issues!

As you know, PHP is a very popular language and it's used by million of people worldwide. This means many bugs and security fixes (that is good!). Subscribe to the PHP internal mailing list if you want to be updated.

Conclusion

In this article, I reported only some best practices for the usage of cryptography in PHP. As I wrote many times, cryptography is hard and you need to be very careful to use it in your application. If you can, hire an expert to review your code or ask for help in the PHP community.

There are many good books and blog about applied cryptography but if I have to suggest only one reference, I recommend to read the book Cryptography Engineering by N.Ferguson, B.Schneier, and T. Kohno.