What’s behind Laravel Encryption/Decryption

Roberto Gallea
5 min readMay 8, 2020

--

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 base64
  • iv: 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 too
  • mac: the Message Authentication Code is a signature used to detect value tampering, generated hashing value and iv. 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:

  1. Initialization Vector is generated by generating 128 or 256 bits (according to the used cipher) of random data
  2. 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
  3. 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:

  1. The json payload is extracted. During extraction it is also validated by ensuring that:
    - It has an array form
    - It containsiv, value and mac fields.
    -iv lenghts is compatible with cipher requirements
    - Themac is valid
  2. Data is decrypted using OpenSSL
  3. 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.

--

--

Roberto Gallea

PhD in computer science, from Palermo, Italy. Involved in all kind of technologic stuff, both sw and hw, applied to a wide range of fields.