martinbean's avatar

@shanely I’m not declaring the setAttribute() and getAttribute() methods, but rather overloading them from the base Eloquent Model class.

When you do something like $user->name = 'Martin', that’s actually calling $user->setAttribute('name', 'Martin') under the hood. Therefore, I used this to automatically encrypt and decrypt model attributes when setting and getting. So $model->blood_type = 'O'; would automatically encrypt that value.

1 like
miso's avatar

The accepted answer works but it is not complete.

Model has attributesToArray() method which does not call getAttribute() method, so if you run attributesToArray() it will not decrypt data.

You have to overload attributesToArray() in the trait:

    public function attributesToArray()
    {
        $attributes = parent::attributesToArray(); // call the parent method

        foreach (static::$encryptable as $key) {

            if (isset($attributes[$key])){

                    $attributes[$key] = Crypt::decrypt($attributes[$key]);

            }
        }
        return $attributes;
    }

There are maybe more methods which need to be overloaded, check the Model.php class.

3 likes
llevvi's avatar

@gibex

@llevvi , for Auth you can use a Custom User provider class. Check here and here The Illuminate\Auth\EloquentUserProvider is another place to check how thing works behind the scenes. Anyway, search (Model::where) is slow,an alternative hash column for email is required to avoid bottlenecks. You can work with this and this

Thanks for the links! They're very helpful! We may end up not encrypting the user email since it is the simplest solution. We need to discuss that further in our project though.

MulryTime's avatar

Use the accepted approach cautiously. It will break unique/foreign key constraints in your database. PostgreSQL doesn't know that two different encrypted strings both resolve to the same string, so it will allow multiple instances of the same string.

1 like
llevvi's avatar

@miso Man! You don't know how I needed your suggestion! Overriding attributesToArray() is definitely what I was looking for! It will save me A LOT of RAM processing and lines of code that I've built so far. Thank you very much.

Michael_Johnson's avatar

I have implemented the accepted answer and it works great for creating and retrieving records from the database. The only problem im having is when it comes to updating and encrypted record. The update method simply stores the value as plain text in the database and doesnt encrypt it. Has anyone been able to solve this issue?

1 like
Michael_Johnson's avatar

@gibex It is almost exaclty the same as the one accepted as the answer


namespace App\Traits;
use Illuminate\Support\Facades\Crypt;

trait Encryptable {

public function getAttribute($key)
    {
    $value = parent::getAttribute($key);
        if (in_array($key, $this->encryptable)&&$value!='') {
            $value = Crypt::decrypt($value);
            return $value;
    }
    return;
}

public function setAttribute($key, $value)
{
    if (in_array($key, $this->encryptable)&&$value!='') {
        $value = Crypt::encrypt($value);
    }
    return parent::setAttribute($key, $value);
}

}

gibex's avatar

@Michael_Johnson Why you don't encrypt all values, even empty ones? What kind of fields do you try encrypt? Can you post the model ?

Michael_Johnson's avatar

@gibex I have changed it now so that it will encrypt everything. The issue is still there, when the record is created the values are encrypted and stored correctly but when I call the update method the new values are not encrypted and are then persisted to the database as plain (readable) strings. The fields I am trying to encrypt are all longText. Is there a way to encrypt data when using the update method?

gibex's avatar

@Michael_Johnson please post the model, trait looks fine, how do you exactly update the field? Do you have a custom update method in Model?

Michael_Johnson's avatar

@gibex This is the encryptable trait again


namespace App\Traits;
use Illuminate\Support\Facades\Crypt;

trait Encryptable {

public function getAttribute($key)
{
    $value = parent::getAttribute($key);
    if (in_array($key, $this->encryptable)) {
        $value = Crypt::decrypt($value);
        return $value;
    }
    return;
}

public function setAttribute($key, $value)
{
    if (in_array($key, $this->encryptable)) {
        $value = Crypt::encrypt($value);
    }
    return parent::setAttribute($key, $value);
}

}

This is the model using the trait


namespace App;

use Illuminate\Database\Eloquent\Model; use App\Traits\Encryptable;

class MemberDetails extends Model {

use Encryptable;
protected $table = 'bankdetails';

protected $fillable = [
    'name',
    'branch',
    'address',
    'city',
    'postCode',
    'completed'
];

protected $encryptable = [
    'branch',
    'postCode',
    'city'
];

}

I have not added a custom update method, I use the one extended from Model eg.


$user->memberDetails()->update($request->except('_token'));
gavro's avatar

The posted solution works perfect most of the time. Thank you for that! But... there can be issues with it.

For example: I've got a custom model mutator where the value is mutated to be uppercase (e.g. setZipAttribute). This logic is executed after Encryptable::setAttribute, thus the DB value is an encrypted string, but then "uppercased", thus it can't be decoded anymore. This is just one simple example... Additionally: You can't be certain that these custom mutators will be returning anything (see Model::setAttribute).

I've changed the setter to prevent these issues. Furthermore, I've wrapped the decryptor in a try/catch. If decode fails: Return a null value. (Why, you might ask? Perhaps you've got an existing site where you are migrating data, you want to have working views at all times.)

namespace App\Traits;

use Illuminate\Support\Facades\Crypt;

/**
 * Class Encryptable
 * @package App\Traits
 */
trait Encryptable
{
    /**
     * @param $key
     *
     * @return mixed
     */
    public function getAttribute($key)
    {
        $value = parent::getAttribute($key);

        if (in_array($key, $this->encryptable)) {
            /*
             * Wrap in try / catch. If not decryptable, return null value.
             */
            try {
                $value = Crypt::decrypt($value);
            } catch (\Exception $e) {
                /*
                 * No-op; perhaps the stored value is still plain text (e.g. migrated recently?).
                 * Return NULL.
                 */
                $value = null;
            }

        }

        return $value;
    }

    /**
     * @param $key
     * @param $value
     *
     * @return $this
     */
    public function setAttribute($key, $value)
    {
        /*
         * We can't be certain anything wil be returned by the parent method (e.g. custom set<SomeThing>Attribute).
         * Execute + get $value again.
         */
        parent::setAttribute($key, $value);
        $value = $this->attributes[$key];

        if (in_array($key, $this->encryptable)) {
            $this->attributes[$key] = Crypt::encrypt($value);
        }

        return $this;
    }
}
1 like
Ori's avatar

Guys you should not encrypt data like this. Database encryption layer is enough. By your way you cannot make complex queries with where clauses. Also in case of bigger queries you will consume more resources.

05mahihbk's avatar

hello sir i implement this with proper code and i am able to insert data into database with encryption but when i get data from database its not decrypting. i mean getAttribute is not working. please provide me a solution.

namespace App\Traits; use Crypt; trait Encryptable { public function getAttribute($key) { $value = parent::getAttribute($key); if (in_array($key, $this->encryptable)) { $value = Crypt::decrypt($value); } return $value; }

public function setAttribute($key, $value)
{
    if (in_array($key, $this->encryptable)) {
        $value = Crypt::encrypt($value);
    }
    return parent::setAttribute($key, $value);
}

}

1 like
tankerkiller125's avatar

@05mahihbk The following is a full solution that actually works and decrypts the data properly.

trait Encryptable {
    public function getAttribute($key)
    {
        $value = parent::getAttributeValue($key);

        if (in_array($key, $this->encryptable)) {
            return $value = \Crypt::decrypt($value);
        }
        return $value;
    }

    public function setAttribute($key, $value)
    {
        if (in_array($key, $this->encryptable)) {
            $value = \Crypt::encrypt($value);
        }

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

ajck's avatar

@martinbean your solution to this looks great, thank you but is it possible to use it with fields that need to be searchable (i.e. appear in where clauses), or how could it be adapted to that if possible? Thanks

Cronix's avatar

@ajck It's not really possible. The only way it would be would be to retrieve all values from the db, decrypt them, and search through the decrypted result which would be crazy to do if you have many records.

The whole point of encryption is to change the values to something that can't be easily deciphered without the key, which means changing it to something completely unintelligible, which would also make it unsearchable.

Avinash57's avatar

Hi,

i need to fetch all relation to a model. Without activating encryption trait, it is working perfect.

but when i activate the encryption trait and try to load the relations using "with", it appears a null value.

Please help me with this.

saroshali4's avatar

Hi,

I'm using the Encryptable trait mentioned in the thread, everything is working fine except the thing that I have to store the user email encrypted as well, then the email stored is used to login. But, encrypt method generates a different encrypted value each time against the same string.

Searching the entire database table and then matching the decrypted value is not a feasible solution.

How I can solve this problem please?

UnraveledMnd's avatar

The best way to approach that is probably to store a hashed version of the email. That way you can search on the hash (which is repeatable).

ACR's avatar

while getting data i am getting encrypted data instead of decrypted data ..

ACR's avatar

namespace App\Support;

//use Config; use Crypt; trait EncryptableTrait { public function getAttribute($key) { $value = parent::getAttribute($key);

    if (in_array($key, $this->encryptable)) {
        $value = Crypt::decrypt($value);
        return $value;
    }
    return $value;
}

public function setAttribute($key, $value)
{
    if (in_array($key, $this->encryptable)) {
        $value = Crypt::encrypt($value);
    }

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

}

Please or to participate in this conversation.