Security Tip: Custom Encryption Key

[Tip#1] - We're starting out with a simple but quite important tip, how to use a custom encryption key for encrypted casting within Models.

Greetings! Firstly, I want to thank every one of you for signing up to Laravel Security in Depth. I wasn't sure what to expect, and it's honestly been incredible to see just how much support I've received. So thank you, I really appreciate it.

We're going to start out with a security tip, something simple but also quite important: using a custom encryption key. Next week (for paid subscribers) we’ll dive further into encryption, looking in depth to see how it works and what options are available for us. This topic follows on nicely from my Laracon talk, where we abused the encryption oracle to gain a root shell.

Don't forget to subscribe, so you don't miss the next one!

I’m new to this whole Substack thing, so bear with me as I figure out how it works and what the different options do. If you’ve used Substack before and have any suggestions, let me know!

If you want to contact me, you can either reply directly to this email, email me at stephen@rees-carter.net or find me on Twitter as @valorin.

Well, that's enough from me. Let's get to that tip you're all waiting for!


How to Use a Custom Encryption Key for Encrypted Cast Model Attributes

Laravel allows you to cast model attributes as encrypted strings1, when stored in the database. This gives you added security for any values that are sensitive (such as Personally identifiable information, PII), including complex structures such as arrays, collections, and even objects.

class User extends Model 
{
    protected $casts = ['secrets' => 'encrypted:array']; 
}

The downside is, Laravel uses the default application encryption key to encrypt the value. This opens up the potential risk of a hacker being able to encrypt a specially crafted payload and then retrieve the encrypted value somehow2.

The solution is quite easy, we can use the Model::encryptUsing() method, which was added to Laravel by Illia Sakovich3. It allows us to define a custom encrypter for use by the Model when encrypting data, which lets us use a different encryption key.

First, generate a new encryption key and add it into .env:

php artisan key:generate --show

Next, load that into your application config - something like database.encryption_key makes sense, given it’s for the database model encryption.

Finally, update your AppServiceProvider to load the new encryption key into the model:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Encryption\Encrypter;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $key = $this->databaseEncryptionKey();
        $cipher = config('app.cipher');
        Model::encryptUsing(new Encrypter($key, $cipher));
    }

    protected function databaseEncryptionKey(): ?string
    {
        $key = config('database.encryption_key');
        return base64_decode(Str::after($key, 'base64:'));
    }
}

You can find all the code in this Gist: https://gist.github.com/valorin/ce58cf55dedaf759b3aa7fcfb2fcf613.

That should do it4.

Your encrypted casts should now use a custom key, keeping your application key safe.

2

As I did in my Laracon talk to gain a root shell.

4

But don’t change your key after data is stored in the database, or you’ll lose access to the original data with the old key!