skip to content

PHP: Basic two-way encryption

 Tweet0 Shares0 Tweets

While the basic PHP crypt function is perfect for encrypting and authenticating passwords, the hash it creates is one-way and doesn't allow any form of decryption.

In this article we explore the art of two-way encryption in PHP which would allow us to insert values into a form which are encrypted and unreadable to the browser, yet easily decipherable on the server after the form has been submitted.

Encrypting a string

For the purpose of encryption we're using the OpenSSL library, in particular the openssl_encrypt function, defined as follows:

openssl_encrypt($string, $method, $key, $options, $iv)

The parameters can be confusing, even after reading the manual, but can basically be thought of as:

Text/Data to be encrypted
Chosen from openssl_get_cipher_methods()
Encryption key (must be kept private)
Unique Random Initialization Vector (IV, or nonce)

Here is some sample code for encrypting a string:

<?PHP $token = "The quick brown fox jumps over the lazy dog."; $enc_method = 'AES-128-CTR'; $enc_key = openssl_digest(gethostname() . "|" . ip2long($_SERVER['SERVER_ADDR'], 'SHA256', true)); $enc_iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($enc_method)); $crypted_token = openssl_encrypt($token, $enc_method, $enc_key, 0, $enc_iv) . "::" . bin2hex($enc_iv); unset($token, $enc_method, $enc_key, $enc_iv); ?> Encrypted string: KZ4LurHESC0Y8/Ufy1wsio6aaYXW7m7KVuW8NBKQhE5CnLspz+540p1ClhIZvKNx::254f830c42c937fb7e1e2444c632a8a4

Note that our output string consists of the encrypted string followed by the Initialisation Vector in hex format (the actual initialisation vector is an unreadable binary value). We've used "::" to separate the two values, but that's not necessary if you know what the IV length is going to be.

IV (hex)

We pass the IV because it's needed in order to perform decryption, and because it's random and unique each time, we can't recreate it through other means. It's similar to the salt used in one-way encryption which then forms part of the hash.

The key on the other hand is an encryption key that we can reproduce. In the example we've used some basic server values (the local hostname and ip address), but please be creative, and always keep it secure. It is also needed for decryption.

The comments section for openssl_encrypt (linked under References below) contains detailed explanations of the different options and algorithms.

Decrypting an encrypted string

As mentioned above, decryption requires that we know or can recreate both the key and the Iniialisation Vector. To that end the decryption process looks very similar to the encryption process above. We start by extracting the encrypted string and IV:

<?PHP if(preg_match("/^(.*)::(.*)$/", $crypted_token, $regs)) { // decrypt encrypted string list(, $crypted_token, $enc_iv) = $regs; $enc_method = 'AES-128-CTR'; $enc_key = openssl_digest(gethostname() . "|" . ip2long($_SERVER['SERVER_ADDR'], 'SHA256', true)); $decrypted_token = openssl_decrypt($crypted_token, $enc_method, $enc_key, 0, hex2bin($enc_iv)); unset($crypted_token, $enc_method, $enc_key, $enc_iv, $regs); } ?> Decrypted string: The quick brown fox jumps over the lazy dog.

If everything went well the decrypted string will match the original input, which it does. If not, check that you haven't missed a binary->hex or hex->binary conversion.

On most servers you will have 100+ encryption methods to choose from, and the $options value which we haven't discussed will also affect the output format. There are any number of ways to generate the $key value and for passing the $iv. Please consider all options carefully.

Note that we're using hexadecimal encoding for the encrypted string and IV because we plan to pass the encrypted string and IV as a form value. For other purposes you would leave them in binary format and use the OPENSSL_RAW_DATA option.

Creating the Cryptor class

With some lessons learned, we can now create a basic PHP class to handle encryption:

// Original PHP code by Chirp Internet: // Please acknowledge use of this code by including this header. class Cryptor { protected $method = 'AES-128-CTR'; // default private $key; protected function iv_bytes() { return openssl_cipher_iv_length($this->method); } public function __construct($key = false, $method = false) { if(!$key) { // if you don't supply your own key, this will be the default $key = gethostname() . "|" . ip2long($_SERVER['SERVER_ADDR']); } if(ctype_print($key)) { // convert key to binary format $this->key = openssl_digest($key, 'SHA256', true); } else { $this->key = $key; } if($method) { if(in_array($method, openssl_get_cipher_methods())) { $this->method = $method; } else { die(__METHOD__ . ": unrecognised encryption method: {$method}"); } } } public function encrypt($data) { $iv = openssl_random_pseudo_bytes($this->iv_bytes()); $encrypted_string = bin2hex($iv) . openssl_encrypt($data, $this->method, $this->key, 0, $iv); return $encrypted_string; } // decrypt encrypted string public function decrypt($data) { $iv_strlen = 2 * $this->iv_bytes(); if(preg_match("/^(.{" . $iv_strlen . "})(.+)$/", $data, $regs)) { list(, $iv, $crypted_string) = $regs; $decrypted_string = openssl_decrypt($crypted_string, $this->method, $this->key, 0, hex2bin($iv)); return $decrypted_string; } else { return false; } } }

expand code box

Not much has changed from the previous examples, except that we're now prepending the $iv to the encrypted string and not using a separator. We can then extract the IV when decrypting because we know that it will be (in hexadecimal format) two characters for each byte, and the byte length is determined by the encryption method.

Using the Cryptor class

Usage is as simple as creating an instance of the Cryptor class and passing a string to the encrypt method:

<?PHP $token = "The quick brown fox jumps over the lazy dog."; $encryption_key = 'CKXH2U9RPY3EFD70TLS1ZG4N8WQBOVI6AMJ5'; $cryptor = new Cryptor($encryption_key); $crypted_token = $cryptor->encrypt($token); unset($token); ?>

The encrypted string can then be submitted via a hidden form field to another PHP script where the value needs to be decoded:

<?PHP $encryption_key = 'CKXH2U9RPY3EFD70TLS1ZG4N8WQBOVI6AMJ5'; $cryptor = new Cryptor($encryption_key); $decrypted_token = $cryptor->decrypt($crypted_token); ?>

As shown above, you can specify the $key for encryption, but if you leave it blank a (possibly less secure) default value will be used.

Simiarly, you can pass an encryption method as the second parameter to override the default:

<?PHP $cryptor = new Cryptor(); // use default key and method $cryptor = new Cryptor($encryption_key); // use default method $cryptor = new Cryptor($encryption_key, $encryption_method); ?>

Whichever option you choose the encryption and decryption must use the same settings for successful decryption.

Please read the StackOverflow answer on Portable Data Encryption which will tell you why the above approach is 'unsafe' for transferring critical data.



Send a message to The Art of Web:

used only for us to reply, and to display your gravatar.


<- copy the digits from the image into this box

press <Esc> or click outside this box to close

Post your comment or question