THIS CONTENT IS OUT OF DATE! IF YOU’RE USING CAKE 2, YOU SHOULDN’T READ THIS.
Here’s a quickie – a Cipher behavior for CakePHP to handle two-way encryption of sensitive data. If you want to store, say, credit card information, you’ll need a way to retrieve it later; Cake’s built-in security hashing is one-way, meaning that once it’s encrypted it ain’t comin’ back. So, I turned to the Zend Framework and brought in Zend_Filter_Encrypt and Zend_Filter_Decrypt to handle the heavy lifting. This behavior uses mcrypt, so you need to have that PHP extension installed, which (as far as I can tell) is fairly standard.
Download the Cipher Behavior Source on Github
First things first – you need to throw a couple of libraries from the Zend Framework into your vendors folder. Yup, I love my Zend! CakePHP teams up very well with ZF.
You’ll need the following Zend Framework libraries (download the framework):
- Zend_Filter (Zend/Filter.php and Zend/Filter)
- Zend_Loader (Zend/Loader.php and Zend/Loader)
Put those files in your vendors folder. You’ll also need to update your include_path somewhere (say, app/bootstrap.php) with the following:
ini_set('include_path', ini_get('include_path') . ':' . APP . '/vendors');
I also recommend defining an autoload function somewhere for Zend classes (again, bootstrap.php is a good choice). A simple, Zend-only autoloader might look like this:
function __autoload($path) {
if (substr($path, 0, 5) == 'Zend_') {
include str_replace('_', '/', $path) . '.php';
}
return $path;
}
The behavior has three config options:
- key: the encryption key to use. If null (default), then it’ll use the value of Configure::read(‘Security.salt’).
- automatic: whether to automatically encrypt and decrypt data as it’s saved/retrieved. Defaults to true.
- fields: an array of fields belong to the model that should be encrypted
Usage of the behavior itself is straightforward: just add Cipher to the $actsAs array of any model you’d like:
Class User extends AppModel {
var $actsAs = array('Cipher');
}
Class Order extends AppModel {
var $actsAs = array('Cipher' => array('fields' => array('credit_card', 'expiry')));
}
You can also manually decrypt and encrypt data, either by passing an array of find results (in which case the fields specified in ‘fields’ will be encrypted/decrypted), or by passing a string:
// Manually encrypt/decrypt the results of Model::find()
$encrypted = $this->encrypt($findResults);
$decrypted = $this->decrypt($encrypted );
// Manually encrypt/decrypt a string
$encryptedSecretString= $this->encrypt('my secret string');
$decryptedString = $this->decrypt($encryptedSecretString);

I like what you have done here. I was wondering though, is there a way to achieve this using the built in cipher($key)? Sorry, I am pretty new to cake php, and I was told to use the cipher behavior that was in the cakephp bakery and modify it from using “blowfish” to using the built in “cipher” and to be honest I am quite lost.
I highly recommend not using Cake’s cipher function, which is found in the Security class. It’s not true encryption. My behavior uses mcrypt, which is a very good php extension that handles two-way encryption. Blowfish is another decent encryption engine. I’d say that you should use either of those before you resort to Cake’s Security::cipher() function, which is a pretty weak encryption tool. Otherwise, I wouldn’t have written this behavior!
Is there a reason why you don’t want to use mcrypt or blowfish?
Works great thank you!
There is one thing, I can’t get right. When doing a query on a model with your encryption behavior through an other model (e.g. habtm query) the data is not decrypted.
Guest habtm Guestaccompanion both with your behavior:
$guests = $this->Guest->find(‘all’);
Outputs the Guest as decrypted the Guestaccompanions not. Is there a trick or do we have to decrypt the Guestaccompanion manually?
I know this is only somewhat related, but I *have* been using CakePHP’s built-in Security::cipher() method for some of my projects. I am now dealing with an unfortunate situation in which this encrypted data is now broken because I mysqldump’d it from the production server down to my local, and then back up again. I didn’t make any changes to the encrypted data at all. But it appears that the export/import routine has messed with the charset maybe? I’m wondering if there’s a way to decipher/decrypt the data back to it’s plain-text state so I can switch to a different library.
I am unsure why you would want to be able to encrypt/decrypt the credit card information given that your key is using the cipher seed that is stored on the server. It’s not any safer, and I don’t see why would you need to decrypt CC information especially when you’re using an encryption that any one can use. If they got their hands on your key, it would be unsafe. It’s much safer to have a one way hash and then compare hashes. Could you explain the reasoning for this, specifically to credit card information since it’s is used as an example in your post?
Sure. Manual processing of credit card information.
I might add that, if someone gains access to your server, nothing is safe anyway.
@Doug, there may also be other instances of using cipher other than for CC data. For instance, social security numbers. Even data like employee numbers, insurance account numbers and the like may all need to be stored so they can be viewed by the appropriate people (i.e. hospital staff or insurance agents). Ciphering allows us to protect this data in the event that the database content gets somehow seen, especially in backup files.
That, however, is what I’m having problems with at the moment. It appears that mysqldump(s) of tables with ciphered data in them cannot be imported back into the same database and correctly de-ciphered. Jamie, do you have any insight into this?