Cryptography is a complex matter. In fact, there is one golden rule:

* Don’t implement cryptography yourself *

The reason for this is that so many things can go wrong while implementing it, the slightest error can generate a vulnerability and if you look away, your precious data can be read by someone else. Whilst this is not an exhaustive list, there are several important guidelines to follow when using cryptography:

Don’t use the same key to encrypt everything

Don’t use a generated key directly to encrypt

When generating values that you don’t want to be guessable, use a cryptographically secure pseudo random number generator (CSPRNG)

Encrypt, then MAC (or the Cryptographic Doom Principle)

Kerckhoffs’s principle: A crypto system should be secure even if everything about the system, except the key, is public knowledge

Some of the cryptographic terms used in this article can be defined as follow:

Key: a piece of information that determines the functional output of a cryptographic algorithm.

CSPRNG: also known as a deterministic random bit generator, is an algorithm for generating a sequence of numbers whose properties approximate the properties of sequences of random numbers (or bytes). To be cryptographically secure, a PRNG must: Pass statistical randomness tests Hold up well under serious attack, even when part of their initial or running state becomes available to an attacker.

MAC: is a short piece of information used to confirm that the message came from the stated sender (its authenticity) and has not been changed in transit (its integrity). It accepts as input a secret key and an arbitrary-length message to be authenticated, and outputs a MAC.

To further read about cryptography and have a better understanding, you can take a look at the following pages:

Some libraries out there implement cryptography primitives and operations, and leave a lot of decisions to the developer. Examples of those are php’s own crypto library, or Defuse’s php-encryption. Some PHP frameworks implement their own crypto like Zend Framework’s zend-crypt or Laravel.

Nevertheless, there is one library that stands out from the rest for its simplicity and takes a lot of responsibility from the developer on the best practices, in addition to using the libsodium library. In this article we are going to explore Halite.

The assumption is that you already have PHP 7, a web server and a MySQL server installed and running. You might want to take a look at Homestead Improved for an environment like this. In order to be able to use Halite, our system must have the libsodium library, as well as the libsodium PHP extension. Those can be installed by using the following commands. As always, depending on your system configuration and installed packages, your mileage may vary:

Install in Ubuntu

sudo apt-get install build-essential wget https://github.com/jedisct1/libsodium/releases/download/1.0.10/libsodium-1.0.10.tar.gz tar -xvf libsodium-1.0.10.tar.gz cd libsodium-1.0.10/ sudo ./configure sudo make sudo make install sudo apt-get install php-pear php7.0-dev sudo pecl install libsodium

Install in CentOS

sudo yum groupinstall "Development tools" wget https://github.com/jedisct1/libsodium/releases/download/1.0.10/libsodium-1.0.10.tar.gz tar -xvf libsodium-1.0.10.tar.gz cd libsodium-1.0.10/ sudo ./configure sudo make sudo make install yum install php-pear php-devel sudo pecl install libsodium

You will have to edit your php.ini file to include the extension=libsodium.so line and restart your web server or php-fpm .

The application

To showcase some of Halite’s basic features, let’s create a set of RESTful services for a simple “e-mail like” messaging application, where we want to encrypt messages sent between users. Please take into consideration that this will be just a tiny and simple example, a lot of standard email features will be missing. Should you like to browse the full source code, please take a look at the github repo.

The database tables have the following schema:

The most relevant fields are:

table field purpose users salt holds a random binary string for each user messages users_id the user id to which the message is intended to messages fromUserId the user id from which the message is sent

Let’s begin by initializing a composer.json file, and then declaring the dependency of our project on the Halite library using Composer.

composer init composer require paragonie/halite

For this example, I’ll be using silex for routing requests into an MVC style application, phpdotenv to store and load environment variables and doctrine orm to interact with the database using php objects.

composer require silex/silex:~2.0 vlucas/phpdotenv doctrine/orm

After successfully downloading the libraries, the composer.json file should be modified to look like the following:

{ "name" : "yourName/projectName" , "description" : "Sitepoint tutorial for Halite" , "authors" : [ { "name" : "yourName" , "email" : "yourEmail" } ] , "require" : { "silex/silex" : "^1.3" , "vlucas/phpdotenv" : "^2.2" , "paragonie/halite" : "^2.1" , "doctrine/orm" : "^2.5" } , "autoload" : { "psr-4" : { "Acme\\" : "src/" } } }

The Acme namespace will hold our custom classes.

The RESTful services in our sample app have the following structure:



Method url description GET /users Get a list of users GET /users/{userid} Get the details of a given user GET /users/{userid}/messages Get a list of messages sent to a given user POST /users/{userid}/messages Send a message to a user GET /users/{userid}/messages/{messageid} Retrieve a message from a given user

For this application, we desire to encrypt the subject and the message, so the urls in bold are the ones that use Halite’s features.

For this article, we will be using symmetric encryption. That is, we use the same key to encrypt and decrypt a message.

Given that we have the following users in the system (by invoking the users list service):

#Request GET /users HTTP/1.1 Host: yourhost.dev Cache-Control: no-cache #Response [ { "id": 1, "name": "John Smith" }, { "id": 2, "name": "Jane Doe" } ]

When we want to sent a message to John, from Jane, then we should send the following request:

POST /users/1/messages HTTP/1.1 Host: yourhost.dev Cache-Control: no-cache { "from": "2", "subject": "This is my subject", "message": "super secret information!!!" }

The sample project uses Silex and Doctrine to do some heavy lifting, however, it is not the scope of this article to show how they work. For that, please see this intro to Silex and some of our Doctrine posts.

Roughly speaking, Silex will forward HTTP requests to a controller. The controller will invoke a service, where things begin to get interesting. Let’s look at the code from the \Acme\Service\Message::save method.

<?php namespace Acme \ Service ; use Acme \ Model \ Message as MessageModel ; use Acme \ Model \ Repository \ Message as MessageRepository ; use Acme \ Model \ User as UserModel ; use Doctrine \ ORM \ EntityManager ; use Doctrine \ ORM \ EntityRepository ; use ParagonIE \ Halite \ KeyFactory ; use ParagonIE \ Halite \ Symmetric \ AuthenticationKey ; use ParagonIE \ Halite \ Symmetric \ Crypto ; use ParagonIE \ Halite \ Symmetric \ EncryptionKey ; class Message { public function save ( $from , $to , $subject , $message ) { $messageModel = new MessageModel ; $userRepository = $this - > em - > getRepository ( 'Acme\Model\User' ) ; $fromUserModel = $userRepository - > find ( $from ) ; $toUserModel = $userRepository - > find ( $to ) ; if ( ! $fromUserModel || ! $toUserModel ) { throw new \ InvalidArgumentException ( 'From or to user does not exist' ) ; } $messageModel - > setFromUser ( $fromUserModel ) - > setToUser ( $toUserModel ) ; $this - > em - > persist ( $messageModel ) ; $this - > em - > flush ( ) ; $toUserSalt = $toUserModel - > getSalt ( ) ; $encryptionKeySubject = KeyFactory :: deriveEncryptionKey ( base64_decode ( $toUserSalt ) . 'subject' . $messageModel - > getId ( ) , base64_decode ( getenv ( 'HALITE_ENCRYPTION_KEY_SALT' ) ) ) ; $encryptionKeyMessage = KeyFactory :: deriveEncryptionKey ( base64_decode ( $toUserSalt ) . 'message' . $messageModel - > getId ( ) , base64_decode ( getenv ( 'HALITE_ENCRYPTION_KEY_SALT' ) ) ) ; $cipherSubject = Crypto :: encrypt ( $subject , $encryptionKeySubject , true ) ; $cipherMessage = Crypto :: encrypt ( $message , $encryptionKeyMessage , true ) ; $messageModel - > setSubject ( base64_encode ( $cipherSubject ) ) - > setMessage ( base64_encode ( $cipherMessage ) ) ; $this - > em - > persist ( $messageModel ) ; $this - > em - > flush ( ) ; return $messageModel - > getId ( ) ; } }

First, an object representing a record from the messages table is created to be used with Doctrine. This will make it easier to write data to the database. This object has attributes that are mapped 1:1 to the messages table.

Next, a Doctrine repository is created for the users table. The repository is used to easily retrieve data into model objects. The ease of retrieving data is shown with the statements $userRepository->find($from) and $userRepository->find($to) all without writing a single SQL command.

The resulting models are then assigned to our message model, and then persisted to the database using Doctrine’s entity manager via the $this->em->persist($messageModel); and $this->em->flush(); statements. This is done so the primary key created for this record can be retrieved and used later on to encrypt the data. For more information about the dual nature of persist/flush, see this post.

Each user in our database holds a unique random bytes string called ‘salt’. This salt, which is base64 encoded in order to be stored in the database as a series of printable characters, looks like this:

We need to retrieve both the sender’s and recipient’s salt to be used to encrypt the message. As you may have already guessed, this is done easily via the models loaded above with the $toUserSalt = $toUserModel->getSalt(); and $fromUserSalt = $fromUserModel->getSalt(); .

Finally, the real fun begins. It is time to use the Halite library. One of the rules of cryptography dictates to never directly use a pregenerated key to encrypt data, as well as to not use the same key multiple times. We will use the \ParagonIE\Halite\KeyFactory class to generate encryption keys. Please look at the following statement carefully:

$encryptionKeySubject = KeyFactory :: deriveEncryptionKey ( base64_decode ( $toUserSalt ) . 'subject' . $messageModel - > getId ( ) , base64_decode ( getenv ( 'HALITE_ENCRYPTION_KEY_SALT' ) ) ) ;

The \ParagonIE\Halite\KeyFactory::deriveEncryptionKey method receives 2 parameters: the password to derive from, and a salt. Please notice that we are using the concatenation of the recipient’s salt (which has to be base64 decoded first), the string ‘subject’ and the message’s unique ID as the password parameter; this will ensure that the ‘password’ is unique for this recipient / this record / this field. The second parameter will hold an application wide salt set as an environment variable thanks to the dotenv library. This salt is stored in a .env file (along with the database credentials) encoded in base64. The .env file holds the salt as follows:

HALITE_ENCRYPTION_KEY_SALT="/t4xwLjjkG0RoRHGkmOQVw=="

This salt is a 16 byte (as per requirements of argon2 algorithm used in Halite’s key derivation function) random binary string. This string can be generated with base64_encode(random_bytes(16)) . For this approach to be secure, the application server and the database server should be on separate hardware.

If both servers are on the same machine, and if an attacker gets access to it, they would have everything they need to decrypt the information. Another approach would be to encrypt the application’s salt using a key that would be provided at service boot time and/or persisted in memory. As long as this key is not intercepted, an attacker would have no way of decrypting anything even if both the web server and the database server are on the same hardware.

Now the actual encryption can take place

$cipherSubject = Crypto :: encrypt ( $subject , $encryptionKeySubject , true ) ; $cipherMessage = Crypto :: encrypt ( $message , $encryptionKeyMessage , true ) ;

The first parameter of \ParagonIE\Halite\Symmetric\Crypto::encrypt is the plain text, the second parameter is the encryption key derived above. The third parameter will tell Halite to return the raw binary string. The default behavior is to return a hex encoded representation. This, of course, depends on your preference.

At the moment we have encrypted a message, so at first glance it won’t be known what the contents are. Halite already makes transparent the fact that it will add an authentication code to the cypher text in order to make sure that the contents of the message have not been tampered with. It will also use a different initialization vector to make sure that the same plain text encrypted with the same key will produce a different cypher text.

Now we are ready to assign the cypher text to our model (base64 encoding them first) $messageModel->setSubject(base64_encode($cipherSubject))->setMessage(base64_encode($cipherMessage)); to be able to persist it to our database.

Let’s see the full request and response

Request

POST /users/1/messages HTTP/1.1 Host: yourhost.dev Cache-Control: no-cache { "from": "2", "subject": "This is my subject", "message": "super secret information!!!" }

Response

{ "messageId": 1 }

If we query the database directly, the record will look like the following:

*************************** 1. row *************************** id: 1 users_id: 1 fromUserId: 2 subject: MUICAV13/brmlurlUIF6TOmhD6duztBI7vYzd4PGrJu8TinhAm69Kzv5QVFpNO55mssxsthPqmwb7l/py1iTCl0tSHUcB5Wsep0bcYDztIPhZ3g7VOcKKqu+YBTcuWprMvM22nvVdzcismGvCjkVW5hqCuNaCJPaUY+7VKRqCLg6FPY4WLMOdbY2yotQ4Q== message: MUICAaSGjcjwaDUORzctiK8rDla+w2Wm/6Z75EP7LJsRZbCk5zVHC/R6oxaJ6VrVbaB6WG1k9xtUcCt9fGN40r3zjFxp4M24QEdVu18t7A8N4wbiwd1W7LqbqjieziBchtKIJjE4oM68BlKxJMGO020GSnwNBuXIaz1b1MRVQDEsm1eT/oTx1oeLvwG1X94raKpHmK7D

If for any reason we were to send exactly the same request:

POST /users/1/messages HTTP/1.1 Host: yourhost.dev Cache-Control: no-cache { "from": "2", "subject": "This is my subject", "message": "super secret information!!!" }

{ "messageId": 2 }

And query the database again:

*************************** 1. row *************************** id: 1 users_id: 1 fromUserId: 2 subject: MUICAV13/brmlurlUIF6TOmhD6duztBI7vYzd4PGrJu8TinhAm69Kzv5QVFpNO55mssxsthPqmwb7l/py1iTCl0tSHUcB5Wsep0bcYDztIPhZ3g7VOcKKqu+YBTcuWprMvM22nvVdzcismGvCjkVW5hqCuNaCJPaUY+7VKRqCLg6FPY4WLMOdbY2yotQ4Q== message: MUICAaSGjcjwaDUORzctiK8rDla+w2Wm/6Z75EP7LJsRZbCk5zVHC/R6oxaJ6VrVbaB6WG1k9xtUcCt9fGN40r3zjFxp4M24QEdVu18t7A8N4wbiwd1W7LqbqjieziBchtKIJjE4oM68BlKxJMGO020GSnwNBuXIaz1b1MRVQDEsm1eT/oTx1oeLvwG1X94raKpHmK7D *************************** 2. row *************************** id: 2 users_id: 1 fromUserId: 2 subject: MUICAeF/ci3fG7YWIhuLCU0pIxpbNDQ10KVWjSZF4G3K8lNVdV81LeveFyhAt/9PvJQy1ePLzl3EupJCROEQ+/L4nHUFRvCekEter2AQvnlyW03cxfjzZ6XwXBUkhzhZrT22JzxL7gSzpC6fPInAzsdDRIqhK/50wPhg/Wr29pH+PT/qwfuKr0rQWdyIfg== message: MUICAYOTzGO7DcpX7bVn4BMH4DKW+JCH1Eacj+lPx9I6eOHjNTA9jHT+u+VEz7cfdiitySU5UYYpBz+IBpV7b8hp/hwkqoLP7Durq/sPRJE/qMY9naKumaPXYW6XMhkysfwnwAIWQhjuiu/r9ARBwdTP3AgFNfVVc/jTlCuaPPZ8jw39xKCO5eNU3UyBrcXFhsGBnS6B 2 rows in set (0.00 sec)

Notice that while the plain text is actually the same in both messages, in the database they appear to be completely different, so even if an attacker had access to the database directly, without the encryption key they would not even be able to tell the messages are related, let alone identical, or find out their contents.

Now it is decryption time!

<?php namespace Acme \ Service ; use Acme \ Model \ Message as MessageModel ; use Acme \ Model \ Repository \ Message as MessageRepository ; use Acme \ Model \ User as UserModel ; use Doctrine \ ORM \ EntityManager ; use Doctrine \ ORM \ EntityRepository ; use ParagonIE \ Halite \ KeyFactory ; use ParagonIE \ Halite \ Symmetric \ AuthenticationKey ; use ParagonIE \ Halite \ Symmetric \ Crypto ; use ParagonIE \ Halite \ Symmetric \ EncryptionKey ; class Message { public function get ( $userId , $messageId ) { $repository = $this - > em - > getRepository ( 'Acme\Model\Message' ) ; if ( ( $message = $repository - > find ( $messageId ) ) == true ) { $toUser = $message - > getToUser ( ) ; if ( $toUser - > getId ( ) == $userId ) { $toUserSalt = $toUser - > getSalt ( ) ; $fromUser = $message - > getFromUser ( ) ; $encryptionKeySubject = KeyFactory :: deriveEncryptionKey ( base64_decode ( $toUserSalt ) . 'subject' . $message - > getId ( ) , base64_decode ( getenv ( 'HALITE_ENCRYPTION_KEY_SALT' ) ) ) ; $encryptionKeyMessage = KeyFactory :: deriveEncryptionKey ( base64_decode ( $toUserSalt ) . 'message' . $message - > getId ( ) , base64_decode ( getenv ( 'HALITE_ENCRYPTION_KEY_SALT' ) ) ) ; $plainSubject = Crypto :: decrypt ( base64_decode ( $message - > getSubject ( ) ) , $encryptionKeySubject , true ) ; $plainMessage = Crypto :: decrypt ( base64_decode ( $message - > getMessage ( ) ) , $encryptionKeyMessage , true ) ; return [ 'id' = > $message - > getId ( ) , 'subject' = > $plainSubject , 'message' = > $plainMessage , 'name' = > $fromUser - > getName ( ) , ] ; } } throw new \ InvalidArgumentException ( 'Message not found' ) ; } }

We get the message table repository with $repository = $this->em->getRepository('Acme\Model\Message'); , and proceed to load the message with $message = $repository->find($messageId) . To decrypt, we have to again derive the encryption key the same way we did when saving the message the first time, only this time we’ll use the \ParagonIE\Halite\Symmetric\Crypto::decrypt method:

$plainSubject = Crypto :: decrypt ( base64_decode ( $message - > getSubject ( ) ) , $encryptionKeySubject , true ) ; $plainMessage = Crypto :: decrypt ( base64_decode ( $message - > getMessage ( ) ) , $encryptionKeyMessage , true ) ;

This method receives 3 parameters: the cypher text, the encryption key, and the flag to tell the method that it should expect raw binary bytes in the cypher text. (remember that we stored it base64 encoded in the database, which is why we need to base64 decode it). Now we are able to perform the request to retrieve a message by ID:

Request

GET /users/1/messages/1 HTTP/1.1 Host: yourhost.dev Cache-Control: no-cache

Response

{ "id": 1, "subject": "This is my subject", "message": "super secret information!!!", "name": "Jane Doe" }

You can take a look at the full source code here.

Conclusion

Encrypting and decrypting is dead simple with Halite. This does not mean that the above example is super secure. There is still a lot of potential for improvement, and Halite has other interesting features which might be showcased in a later article. Remember not to take the above implementation as production ready – it’s only for educational purposes.

How do you do encryption / decryption in PHP? Have you used Halite? Let us know!