Security Tip: Custom Encryption Keys for Cast Model Attributes
[Tip#1] A simple but quite important tip, how to use a custom encryption key for encrypted casting within Models.
👉 Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code. 🕵️
👉 When was your last Security Audit or Penetration Test? Book in a Laravel Security Audit and Penetration Test today! 🕵️
Laravel allows you to cast model attributes as encrypted strings1, when storing them 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 though, 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
might make 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.
As I’ve done in past Laracon Online talks to gain a root shell.
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!