Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Re-encryption after APP_KEY rotation
<?php
namespace App\Providers;
use App\Encrypter;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton('encrypter', function($app){
$config = $app->make('config')->get('app');
if (Str::startsWith($key = $config['key'], 'base64:')) {
$key = base64_decode(substr($key, 7));
}
return new Encrypter($key, $config['cipher']);
});
}
}
<?php
namespace App;
use Illuminate\Support\Str;
class Encrypter extends \Illuminate\Encryption\Encrypter
{
/**
* Decrypt the given value.
*
* @param string $payload
* @param bool $unserialize
* @return mixed
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function decrypt($payload, $unserialize = true)
{
try{
return parent::decrypt($payload, $unserialize);
}catch(\Throwable $e){
$currentKey = $this->key;
$this->key = Str::startsWith(config('app.old_key'), 'base64:')
? base64_decode(substr(config('app.old_key'), 7))
: config('app.old_key');
return tap(parent::decrypt($payload, $unserialize), function () use ($currentKey) {
$this->key = $currentKey;
});
}
}
}
@anouarabdsslm
Copy link

thanks you for this 👍

@Radiergummi
Copy link

@themsaid I know this is a pretty old example, but still the only semi-official response on rotating keys by the Laravel maintainers. I just got bitten by the tap call - if the second decryption attempt fails, the callback never runs, and the key will not be reset.
This is usually no problem, but in Octane applications, the Encrypter instance stays alive after the request is complete. An easy fix would be to use try-finally instead:

try {
    return parent::decrypt($payload, $unserialize);
} finally {
    $this->key = $currentKey;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment