Encrypting Laravel Eloquent Models

October 24, 2014

Suppose you have a database table with one or more private fields like a Social Security Number or Passport ID. You want your application to have access to this data but you also want to make sure that in the worst case scenario of a data breach this information remains protected and useless to an attacker. Laravel "Provides facilities for strong AES encryption via the mcrypt PHP extension". This makes things really easy to add two-way encryption to selected fields in an Eloquent model using a really simple pattern.

Two-way encryption is not to be confused with storing passwords. Hashing a password is a one-way function and is outlined here. A common pattern for ensuring that passwords on your User model are always encrypted is to override the password mutator in your User model.

<?php

class User extends Eloquent implements UserInterface {
...
    public function setPasswordAttribute($value) 
    { 
        if (!empty($value)) 
            $this->attributes['password'] = Hash::make($value);
    }
...
}

For encrypting and decrypting selected fields on a model, we must call the Crypt::encrypt() and Crypt::decrypt() functions. We can do this from a base model when getting or setting an attribute like so:

<?php

class BaseModel extends \Eloquent {

    protected $encrypted;

    public function getAttribute($key)
    {
        if (array_key_exists($key, array_flip($this->encrypted)))
        {
            return Crypt::decrypt(parent::getAttribute($key));
        }

        return parent::getAttribute($key);
    }

    public function setAttribute($key, $value)
    {
        if (array_key_exists($key, array_flip($this->encrypted)))
        {
            parent::setAttribute($key, Crypt::encrypt($value));
            return;
        }

        parent::setAttribute($key, $value);
    }
}

What this code does is declare an $encrypted variable which will store the names of the encrypted fields on a model. It then overrides the getAttribute() and setAttribute() functions of the Illuminate\Database\Eloquent\Model class. Inside of these overrides we just check if the key being accessed or mutated is in the $encrypted array and then return its decrypted value or set its encrypted value. That's it!

Now, in each of your model classes, all you need to do is extend the BaseModel and declare an array of $encrypted fields. Underlying database columns must be text or varchar.

<?php

class MyModel extends BaseModel {

    protected $encrypted = [ 'social_security_number', 'passport_id' ];
}

Then it's just a matter of setting these fields as normal.

$instance = new MyModel();
$instance->social_security_number = 'Top secret';
$instance->insecure_field = 'Not a secret';
$instance->save();
...
echo $instance->social_security_number; // 'Top secret'

If you check your database you'll see that my_model.social_security_number is now encrypted.

It is important to note that any part of your application will have access to this unencrypted data. This method only encrypts what is stored in the database. So be sure to restrict this data appropriately for different users of your application.

The last thing to do is set the key property in app/config/app.php. For more security, it is a good idea not to add this directly to the file that may be saved in source control, but rather use an environment variable that lives only on your production server.

'key' => isset($_ENV['ENCRYPTION_KEY']) ? $_ENV['ENCRYPTION_KEY'] : 'some default',
 

comments powered by Disqus

About This Site

This site was designed by We Are How.

This site is powered by Sculpin static site generator and the source is available here.

Yotta = 10^24, or 1 000 000 000 000 000 000 000 000, the largest metric prefix.


Contact

Get in touch to find out how we can help you refine your vision and implement a dynamite product that will help your business grow. Our agile product development process is thoughtfully designed to give clients ongoing feedback and visibility from project inception to completion.