Data Encryption with Eloquent – Laravel

Data Encryption

Few weeks back, one of my client asked me about the possibility of data encryption of his customers data, stored in database. It would be unusable, even if someone got the dump of those data. But those data should be usable and readable in his existing applications.

My client has app built on built on Laravel. So I looked if there is any existing solution and found some existing Laravel packages. But I decided to dive in myself so that I’d have full control over the process.

I decided to go with PHP Trait and use Crypt provided by laravel for encryption/decryption. And here is my solution.

Encryption Config

Add a variable DATA_ENCRYPTION_ENABLED in .env file of the project and set to true

// .env
....
DATA_ENCRYPTION_ENABLED=true
....

Create constants.php file in config folder or if already have that file in configs folder, add following parameter:

// constants.php

return [
....
    'data_encryption_enabled' => env('DATA_ENCRYPTION_ENABLED', false)
....
];

This config will tell Encryptable Trait, that we will create in a while, to encrypt data or not. By default, data encryption is disabled. Must set DATA_ENCRYPTION_ENABLED to true in .env file to enable encryption.

Data Encryption with Encryptable Trait

Create Traits folder in app/, then create Traits/Encryptable.php file, then place the entire code in app/Traits/Encryptable.php:

// Encryptable.php

namespace App\Traits;

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;

trait Encryptable
{
    public function attributesToArray()
    {
        $attributes = parent::attributesToArray();
        foreach ($this->getEncryptable() as $key) {
            if (array_key_exists($key, $attributes)) {
                $attributes[$key] = $this->decryptAttribute($attributes[$key]);
            }
        }
        return $attributes;
    }

    public function getAttributeValue($key)
    {
        if (in_array($key, $this->getEncryptable())) {
            return $this->decryptAttribute($this->attributes[$key]);
        }
        return parent::getAttributeValue($key);
    }

    protected function decryptAttribute($value)
    {
        try {
            return Crypt::decrypt($value);
        } catch (DecryptException $e) {
            return $value;
        }
    }

    public function setAttribute($key, $value)
    {
        if (config('constants.data_encryption_enabled') && in_array($key, $this->getEncryptable())) {
            $this->attributes[$key] = Crypt::encrypt($value);
        } else {
            parent::setAttribute($key, $value);
        }

        return $this;
    }

    public function getEncryptable()
    {
        return property_exists($this, 'encryptable') ? $this->encryptable : [];
    }
}

Here, attributesToArray() and getAttributeValue() methods will overwrite default eloquent methods with additional steps to decrypt encrypted data. If data not encrypted, data returned as is.

setAttribute() method will overwrite default eloquent method and add encryption step before saving to database. Encryption config status must be enabled to encrypt attributes as per set in model.

Usage

We can now use Encryptable Trait in any Eloquent model that we wish to apply encryption to. But we first need to define a protected $encryptable array that contains the list of the attributes to encrypt.

use App\Traits\Encryptable;

    class User extends Eloquent {
        use Encryptable;

        /**
         * The attributes that should be encrypted to save.
         *
         * @var array
         */
        protected $encryptable = [
            'name', 'address', 'phone'
        ];
    }

Encrypting Current Data

Since my client already had data saved in database, I also needed a method to encrypt those data. So I created an artisan command that implements eloquent models. It uses Encryptable Trait to encrypt existing data. In routes/console.php file, I had this artisan command.

// routes/console.php
....
Artisan::command('encryptable:encryptModel', function () {
    $model = $this->ask('Model?');
    $model = new $model;
    $encryptable = $model->getEncryptable();
    $model->all()->each(function ($item) use ($encryptable) {
        foreach ($encryptable as $field) {
            $item->$field = $item->getOriginal($field);
        }
        $item->save();
    });
});
....

And to encrypt existing data, I just had to call php artisan encryptable:encryptModel command from cli and pass required model path.

$ php artisan encryptable:encryptModel
Model?
> App/User

And I had another artisan command, if I needed to decrypt the encrypted data in database itself.

Artisan::command('encryptable:encryptModel', function () {
    $model = $this->ask('Model?');
    $model = new $model;
    $encryptable = $model->getEncryptable();
    $model->all()->each(function ($item) use ($encryptable) {
        foreach ($encryptable as $field) {
            $item->$field = $item->getOriginal($field);
        }
        $item->save();
    });
});

Run php artisan encryptable:decryptModel and pass required model path to decrypt data.

$ php artisan encryptable:decryptModel
Model?
> App/User

Hope this was useful.

You May Also Like