UPDATE: I’ve posted an improved version of this behavior here
I’ve written the following behavior for a project I recently completed in Cake, and I thought it would be worth sharing:
class CryptableBehavior extends ModelBehavior { var $settings = array(); function setup(&$model, $settings) { if (!isset($this->settings[$model->alias])) { $this->settings[$model->alias] = array( 'fields' => array() ); } $this->settings[$model->alias] = array_merge($this->settings[$model->alias], $settings); } function beforeFind(&$model, $queryData) { foreach ($this->settings[$model->alias]['fields'] AS $field) { if (isset($queryData['conditions'][$model->alias.'.'.$field])) { $queryData['conditions'][$model->alias.'.'.$field] = $this->encrypt($queryData['conditions'][$model->alias.'.'.$field]); } } return $queryData; } function afterFind(&$model, $results, $primary) { foreach ($this->settings[$model->alias]['fields'] AS $field) { if ($primary) { foreach ($results AS $key => $value) { if (isset($value[$model->alias][$field])) { $results[$key][$model->alias][$field] = $this->decrypt($value[$model->alias][$field]); } } } else { if (isset($results[$field])) { $results[$field] = $this->decrypt($results[$field]); } } } return $results; } function beforeSave(&$model) { foreach ($this->settings[$model->alias]['fields'] AS $field) { if (isset($model->data[$model->alias][$field])) { $model->data[$model->alias]['cleartext_'.$field] = $model->data[$model->alias][$field]; $model->data[$model->alias][$field] = $this->encrypt($model->data[$model->alias][$field]); } } return true; } public function encrypt($data) { if ($data !== '') { return base64_encode(mcrypt_encrypt(Configure::read('Cryptable.cipher'), Configure::read('Cryptable.key'), $data, 'cbc', Configure::read('Cryptable.iv'))); } else { return ''; } } public function decrypt($data, $data2 = null) { if (is_object($data)) { unset($data); $data = $data2; } if ($data != '') { return trim(mcrypt_decrypt(Configure::read('Cryptable.cipher'), Configure::read('Cryptable.key'), base64_decode($data), 'cbc', Configure::read('Cryptable.iv'))); } else { return ''; } } } |
All you need to do is add three lines to your bootstrap, and then load the behavior in any model you want to use it.
Here are the lines for your bootstrap:
Configure::write('Cryptable.cipher', 'rijndael-192'); Configure::write('Cryptable.key','random key string here'); Configure::write('Cryptable.iv', base64_decode('base64 encoded IV here')); // Create with mcrypt_create_iv with the appropriate size for your cipher |
Here’s an example of how to load it in your model:
var $actsAs = array( 'Cryptable' => array( 'fields' => array( 'password' ) ) ); |
If you need to encrypt or decrypt a field outside of the normal find methods, you can simply call those methods on the model, passing in the string that needs worked on.
Download Cyptable Behavior

I would suppose you could just use config/core.php salt for this too?
For the encryption key itself, yes. But you still need to set the IV and the encryption scheme to use.
What about the Security::cipher method? Wouldn’t be easier to use that instead?
It would (arguably) be easier, but it’s not as flexible.
This allows you to choose the exact cipher system you want to use, including the IV and the passphrase, and does it all automatically on find and save calls, so you rarely ever have to call the encrypt and decrypt methods manually. It also avoids the extra layer of objects that you would add by using the Security class. Not a major savings, but it adds up.
Also, I just went and looked at the source code for Security::cipher. I’m not confident that it is cryptographically secure. It’s probably secure against a casual attack, but it doesn’t look very robust.
[...] From @utoxin: a behavior to automatically encrypt and decrypt a DB field. [...]
Great behaviour, a part from being useful, it’s very nice to see people that know how to use cake!
Could you elaborate a bit more on creating the IV please? I bet it’s dead obvious, but i’m not sure how to create that.
Thanks
Will
This is the basic idea for creating the IV.
This was EXACTLY what I needed, thank you SO much! You rock!!
I do not have sufficient knowledge about cryptography but a simple need to encode some data. I successfully used above behavior but am not sure which MCRYPT_MODE_mode I should use. I used these:
mcrypt_get_iv_size(MCRYPT_RIJNDAEL_192, MCRYPT_MODE_NOFB)
Can you advice please?
I use CBC for mine, but if you have your own preference, there’s nothing wrong with using it, as long as it works. This behavior is just supposed to automate the handling of it for you.
Decoding does not work when I call ModelWithEncryptedFields in containable behavior, i.e.:
$TopModel = $this->TopModel->find('all',array(
'contain'=>array(
'RelatedModel' => array('SubModelWithEncryptedFields'),
)
));
Above produces fields that are not decrypted automatically. Works fine outside Containable behavior.
Have you got any idea how to overcome this but still use Containable behavior in find?
Unfortunately, this isn’t solvable in any automatic way. The root of the problem is that cake doesn’t run callbacks when retrieving data for associated models. The easy solution is to just call $this->Model->decrypt() on the field you need to use.
Can I do it somehow in the view while displaying the value or shall I re-process find in the controller using foreach on the find result and applying decrypt function to fields in question?
Thanks for previous answers. They have been helpful.
In the controller, after retrieving the data, do something like this:
$data['Mode']['password'] = $this->Model->decrypt($data['Model']['password']);
You can do it in a loop if you have multiple results.
I was hoping I could avoid reprocessing the find since I already go through all the records displaying them and I need to just show one encrypted field at that view. Anyway, its a valid solution and thanks again.
You could technically do it in the view like this:
echo ClassRegistry::init(‘ModelClass’)->decrypt($data['Model']['field']);
Works perfectly. Although this is my preference I am not sure about consequence of doing it.
Is calling ClassRegistry::init() in a loop (up to 100 records) resulting in a bigger performance issue then just re-doing the loop after find in the controller with $this->TopModel->RelatedModel->EncryptedModel->decrypt() ?
I’ve done some some research on ClassRegistry::init(). Are you able to shed light on the following:
I’ve read a comment that subsequent ClassRegistry::init() will not load a new model but work on the same instance. Yet, at another comment I’ve read that following will re-use the instance of the same object:
// before the loop
$LocalModel = ClassRegistry::init(‘Profile’);
// within the loop:
echo $LocalProfile->decrypt($Array['EncryptedField'])
I’d have to go check, but I’m pretty sure ClassRegistry uses the object cache, so it shouldn’t create a whole new instance each time. That said, it would still be more efficient to store the instance locally and use it directly than go through the ClassRegistry every time, regardless.
Both solutions above I implemented and they work but I see no noticeable impact on performance.
Yeah. Unless you’re dealing with a significant (10,000+) number of rows, it’s not going to make a noticeable difference. But still better to go the most efficient route.
Thank you. I’ll stay with the $LocalProfile->decrypt($Array['EncryptedField']) solution. Doing more research into this would not be effective time management, asking you more would be negatively impacting your time management
. Great help!
Hi. Tnx for sharing. I was just in the middle of writing very similiar behavior. Thank you for saving me precious minutes
No problem. If you want slightly improved security (marginal, but an improvement), make sure to use the version in the post I link to at the top of this one now.
how to apply order by on an encrypt information?
Unfortunately, there is no way to order encrypted information using the standard model methods.
Any restrictions on the use of your code? I’d like to use it in a commercial project, and just want to make sure that’s okay with you.
No restrictions. Consider it public domain.
Awesome!
Had some problems with the IV {Error: The IV parameter must be as long as the blocksize}, worked when I used this:
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
$iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
Configure::write(‘Cryptable.iv’, base64_encode($iv));
Thanks for your tip. It save my time anymore!
Will it work in C2.x?
I haven’t tested it, so I’m not sure. I’m on vacation this week, but when I get back home I can test that.
I can confirm that this works in Cake 2.2. First of all, great job on the behavior; it’s really been very useful! I have found one problem with it though, and I thought I’d share my solution. As a disclaimer, I’m still pretty new to Cake PHP, so there might be a better way to do this. If there is, I would greatly appreciate any input.
Anyhow, with that said, this behavior doesn’t work if you are encrypting a foreign key. It does successfully decrypt the foreign key after the find, but unfortunately that is too late for Cake to hook up the model associations automatically. You may ask why anyone would want to encrypt the foreign key? You may say why not just encrypt the data in the model the foreign key is pointing to? That is a good start and I am already doing that. However, not encrypting the foreign key can leak information, which is never a good thing. In my case, I am building an information portal that provides a central place for individuals to keep track of items such as vehicles, medical records, bank accounts, etc. So if I don’t encrypt the foreign key, without knowing any of the information, I could hypothetically learn that a user has 3 vehicles, has a lot of medical records and may possibly be sick, has a checking account and a 401K, etc. This could definitely be useful information to hackers.
Here is my fix for this problem contained in the afterFindMethod (look for the comment about handling the case when the foreign key is encrypted).
function afterFind(Model $model, $results, $primary) {
foreach ($this->settings[$model->alias]['fields'] AS $field) {
if ($primary) {
foreach ($results AS $key => $value) {
if (isset($value[$model->alias][$field])) {
$decryptedValue = $this->decrypt($value[$model->alias][$field]);
$results[$key][$model->alias][$field] = $decryptedValue;
//handles case when a foreign key is encrypted
if(substr($field, -3) == ‘_id’){
$fkModel = Inflector::humanize(substr($field, 0, -3));
$results[$key][$fkModel] = $model->$fkModel->findById($decryptedValue)[$fkModel];
}
}
}
} else {
if (isset($results[$field])) {
$results[$field] = $this->decrypt($results[$field]);
}
}
}
return $results;
}
Thanks for the feedback, and the information. I had never thought of encrypting foreign keys, but if it works for you, great.
I am using your behavior with great success, however I noticed that each time the encrypt method is called, I am getting error messages in my logs. Size of key is too large for this algorithm. I used my salt string from the core.php as the key. How long should the key be? The odd thing is that even though it logs an error message, the behavior successfully encrypts and decrypts the data.
Doublecheck the encryption method you’re using on the mcrypt docs, and see what it says about the key length. It probably truncates it if it’s too long.