Aufgrund eines privaten Projektes hatte ich den Bedarf, Daten in Laravel zu verschlüsseln - zusätzlich zu den Dingen, die standardmäßig durch den App Key verschlüsselt werden. Laravel bietet hierfür die sehr einfachen Funktionen encrypt() und decrypt(), welche ebenfalls den App Key nutzen. Doch was ist, wenn man Dinge pro User verschlüsseln und dafür separate Keys verwenden möchte? Man könnte es kompliziert machen und Laravel Passport nutzen, doch das war mir etwas zu viel des Guten. Ich wollte eine einfache, möglichst automatische Lösung, ohne zusätzliche Userinteraktion.
Hierfür entwickelte ich ein kleines Proof of Concept. Ausgehend wird mittels laravel new ein neues Laravel Projekt angelegt. Danach binden wir mit php artisan make:auth
die Authentifizierungsfunktionen ein. Hierdurch werden neue Migrations angelegt. Da wir pro User einen Encryption Key speichern möchten, müssen wir die User Migration erweitern. Der Key wird als Binary gespeichert, da der später verwendete Schlüsselgenerator auch non-ASCII Zeichen ausgeben kann: $table->binary('key');
. Außerdem möchten wir einen Wert ablegen, der ver- und entschlüsselt wird: $table->text('secret')->nullable();
. Nun müssen wir dem User Model beibringen, selbstständig beim erstellen einer neuen Instanz den User Key anzulegen. Hierfür können wir ganz einfach den Constructor erweitern:
public function __construct(array $attributes = array()) {
parent::__construct($attributes);
$this->key = encrypt(openssl_random_pseudo_bytes(32));
}
Die Keylänge beträgt 32 Zeichen, was 256 Bit sind. Zusätzlich wenden wir die encrypt() Funktion auf den Key an. Durch diesen zusätzlichen Sicherheitsfaktor können die Werte selbst dann nicht verschlüsselt werden, wenn die DB kompomitiert wird. Es wird zusätzlich der App Key aus den Umgebungsvariablen benötigt. 100%ige Sicherheit erreicht man dadurch nicht, aber immerhin besser als den user-spezifischen Key direkt auf der DB abzulegen. Nun muss der zu verschlüsselnde Wert ver- und entschlüsselt werden. Hierfür erweitern wir kurzerhand das Template home.blade.php um folgenden Inhalt:
<div class="card">
<div class="card-header">Secret</div>
<div class="card-body">
Current Secret: {{ $secret }}
<form action="/secret" method="POST">
@csrf
<textarea name="secret"></textarea>
<input type="submit">
</form>
</div>
</div>
Wir erweitern dann die Routen in der web.php um folgenden Eintrag: Route::post('/secret', 'HomeController@set_secret');
. Als letztes erweitern wir den HomeController. Als erstes in der Index-Methode: Hier müssen wir das entschlüsselte $secret an das View geben:
public function index(){
// decrypt secret with user key
$user = \App\User::find(Auth::user()->id);
if ($user->secret != null) {
$key = decrypt($user->key);
$encrypter = new \Illuminate\Encryption\Encrypter($key, 'AES-256-CBC');
$decrypted = $encrypter->decrypt($user->secret);
} else {
$decrypted = "Nothing to decrypt.";
}
return view('home', ['secret' => $decrypted]);
}
Um den verschlüsselten Text zu ändern fügen wir die Methode set_secret hinzu:
public function set_secret(Request $request) {
$secret = $request->input('secret');
$user = \App\User::find(Auth::user()->id);
$key = decrypt($user->key);
$encrypter = new \Illuminate\Encryption\Encrypter($key, 'AES-256-CBC');
$encrypted = $encrypter->encrypt($secret);
$user->secret = $encrypted;
$user->save();
return redirect('home');
}
Und fertig ist das Ganze!