What’s behind Laravel Encryption/Decryption
My system is safe, it uses encryption.
You heard/said this from time to time. Sure, but why and how is it safe? Do you really know this?
Laravel encryption/decryption fundamentals
Laravel encryption/decryption is based on the Illuminate\Encryption\Encrypter
class, which is constructed passing an encryption key and a cipher (i.e. the encryption algorithm):
It supports (among the others) the following main methods:
encrypt($value, $serialize = true)
decrypt($payload, $unserialize = true)
which, not surprisingly, are used to encrypt and decrypt data.
$encrypter = new Illuminate\Encryption\Encrypter('1234567812345678', 'AES-128-CBC');$encrypted = $encrypter->encrypt('Hello world');dump($encrypted);// prints something similar to "eyJpdiI6ImdMd2dWcW5jMXBrUDBranRJZXQ5MEE9PSIsInZhbHVlIjoiNnhTODBSclB3ZVp3SFRRUWFWTHpReFQwYWQ1aXVmTmhXOXV5WHM2TzR1WT0iLCJtYWMiOiIwODQyZDhiMzZlNDQwZTZjYTRiYmI2MGE0MTgzNzk5NGNkZTU1Yzc5NDIyYzdjYmYwNzk2ZTA5MGNjYjc4MGYzIn0="$decrypted = $encoder->decrypt($encrypted);dump($decrypted); // prints "Hello world" again
This is great! And it is enough to use it in the best way.
However, if you want to know what happens under the hood, keep reading.
Note that the results wouldn’t be the very same since some values are computed randomly, so changes at every run.
How encrpytion works
Laravel’s encrypter currently uses OpenSSL for performing AES-256 and AES-128 encryption. It also uses Message Authentication Code (MAC) protection, a mechanism to ensure data is not tampered after encryption.
What’s in the result?
Recalling the previous example, you may think that the encrypted string "eyJpdiI6ImdMd2dWcW5jMXBrUDBranRJZXQ5MEE9PSIsInZhbHVlIjoiNnhTODBSclB3ZVp3SFRRUWFWTHpReFQwYWQ1aXVmTmhXOXV5WHM2TzR1WT0iLCJtYWMiOiIwODQyZDhiMzZlNDQwZTZjYTRiYmI2MGE0MTgzNzk5NGNkZTU1Yzc5NDIyYzdjYmYwNzk2ZTA5MGNjYjc4MGYzIn0="
is by itself the ciphered version of the input. This is definitely true, but there is more to know about.
Indeed, it is a base64 conversion of a string. “What string?” you may ask… And you can get an answer by simply run:
$encrypted = $encrypter->encrypt('Hello world');$decodedEncrypted = base64_decode($encrypted);
which results in a json string similar to the following:
{
"iv":"gLwgVqnc1pkP0kjtIet90A==",
"value":"6xS80RrPweZwHTQQaVLzQxT0ad5iufNhW9uyXs6O4uY=",
"mac":"0842d8b36e440e6ca4bbb60a41837994cde55c79422c7cbf0796e090cc"
}
Now is more opaque than clearer… What is this?
This document is composed by the three main parts of encryption:
value
: the actual ciphered data, coded in base64iv
: the Initialization Vector is a randomly generated fixed-size data sequence inject at each run, preventing semantic-based attacks, see (Initialization vector - Wikipedia for more details). It is base64-coded toomac
: the Message Authentication Code is a signature used to detectvalue
tampering, generated hashingvalue
andiv
. It is represented in hex-string format
Note that both iv
and value
are base64 encoded too, since they are generic bytes sequence and may contain not printable values.
How encryption works — looking at the code
To understand how the payload is generated, let’s give a closer look to the encrypt()
method:
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(openssl_cipher_iv_length($this->cipher)); $value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
); if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
} $mac = $this->hash($iv = base64_encode($iv), $value); $json = json_encode(compact('iv', 'value', 'mac'), JSON_UNESCAPED_SLASHES); if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
} return base64_encode($json);
}
Looking at the code, 5 steps are performed:
- Initialization Vector is generated by generating 128 or 256 bits (according to the used cipher) of random data
- Encrypted Value is generated on lines by running OpenSSL over a (possibly) serialized version of the clear text data, using the chosen cipher, encryption key and IV. Note that the results is base64-coded
- The MAC is generated by the
hash()
method, fed with base64 iv and value. Hashing is defined as:
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
i.e. the SHA256 hashing of the concatenation of IV and value, using the provided encryption key.
4. An array containing `iv`, `value` and `mac` is generated and converted to json
5. The json is encoded in base64 and finally returned
How decryption works — in depth
To understand how clear data is recovered, let’s give a closer look to the decrypt()
method:
public function decrypt($payload, $unserialize = true)
{
$payload = $this->getJsonPayload($payload);$iv = base64_decode($payload['iv']);// Here we will decrypt the value. If we are able to successfully decrypt it
// we will then unserialize it and return it out to the caller. If we are
// unable to decrypt this value we will throw out an exception message.
$decrypted = \openssl_decrypt(
$payload['value'], $this->cipher, $this->key, 0, $iv
);if ($decrypted === false) {
throw new DecryptException('Could not decrypt the data.');
}return $unserialize ? unserialize($decrypted) : $decrypted;
}
Looking at the code, 5 steps are performed:
- The json payload is extracted. During extraction it is also validated by ensuring that:
- It has an array form
- It containsiv
,value
andmac
fields.
-iv
lenghts is compatible with cipher requirements
- Themac
is valid - Data is decrypted using OpenSSL
- Result is (possibly) unserialized and returned
Why is it secure?
This scheme provides security until the encryption key is kept secret. Let’s see why:
- confidence: the clear text message can be recovered only by who knows the secret key
- integrity: if value is modified, decryption fails. If iv and value are both modified, the message could be potentially decryptable, but MAC protection will detect tampering and decryption fails. In any case, varying any combination of iv and/or value and/or mac, decryption fails due to payload corruption.
- The only way to deceive MAC protection is by knowing the encryption key, which allows to forge new valid ciphered full payloads.
Let’s try: create a different encrypted message:
$encrypted2 = $encrypter->encrypt('Hello hacker');$decodedEncrypted2 = json_decode(base64_decode($encrypted2), true);dump('DECODED ENCRYPTED 2: ');
var_dump($decodedEncrypted2);
Now, in turn, try to tamper one or more of the three values and attempt to decrypt the result.
// swapping the ciphered data and try to decipher
try {
$tampered = $decodedEncrypted;
$tampered['value'] = $decodedEncrypted2['value'];
$encrypter->decrypt(base64_encode(json_encode($tampered)));
} catch (\Illuminate\Contracts\Encryption\DecryptException $exception) {
dump($exception->getMessage());
}// swapping the iv and try to decipher
try {
$tampered = $decodedEncrypted;
$tampered['iv'] = $decodedEncrypted2['iv'];
$encrypter->decrypt(base64_encode(json_encode($tampered)));
} catch (\Illuminate\Contracts\Encryption\DecryptException $exception) {
dump($exception->getMessage());
}// swapping the MAC and try to decipher
try {
$tampered = $decodedEncrypted;
$tampered['mac'] = $decodedEncrypted2['mac'];
$encrypter->decrypt(base64_encode(json_encode($tampered)));
} catch (\Illuminate\Contracts\Encryption\DecryptException $exception) {
dump($exception->getMessage());
}
In each of the three cases the MAC control will fail preventing message decryption and DecryptException
is raised.
Conclusion
Now you should understand more in detail how Laravel encryption works under the hoods. Nothing changes in how you use it, but you earned more confidence in the tools you used. Moreover, you can now justify with your customer “ how” your system is safe.
Originally published at http://github.com.