Cryptable Behavior

by utoxin
A behavior that will automatically encrypt/decrypt specified fields in a model, using your choice of cipher, key, and IV.
I've written the following behavior for a project I recently completed in Cake, and I thought it would be worth sharing:


<?php
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:


<?php
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:


<?php
    
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.

Report

More on Behaviors

Advertising

Comments

  • mrteufel posted on 07/14/11 04:37:25 PM
    Hi,
    This helped me a lot developing my super-secure site! Thanks!
    ... but ...
    There must be a way to make it even more secure by not storing the key&iv permanently, so even if someone gets hold of the database and the php scripts they are still not able to read the data.

    My scenario is the following:
    1) sensitive data is stored in a database
    2) many users have access to view or edit parts or the whole of this data
    3) there is a single field of a single table that holds the sensitive data - all other fields can go unencrypted

    So far I came up with this:
    1) every user chooses a passphrase
    2) a master key (used to encrypt the data) is encrypted with (the hash) this passphrase
    3) the hash of the user's passphrase is stored linked to the user as their password hash.
    So upon login the password serves as a password / passphrase.
    The user can login if the stored password hash matches with the provided passphrase's hash.
    [The master password is generated once, when the first user (admin) registers. After that only this admin (or another user with this privilege can create new users).] After login the decrypted master key can be stored in the session, if SSL/TLS is in use. (First question: is this safe enough? Is there a better way?)
    And the main question:
    So far everything works except I can't keep the MVC architecture AND be able not to store the key. (I can also store it a temporary table, but that is very unsafe - if an attacker gets hold of the database when someone is logged in, they can see the master key unencrypted...)
    Can anyone help me out with this?
    Thanks mrt
  • Ferco posted on 02/26/11 04:04:03 PM
    I implemented this behavior in my proyect with h2osoft modifications and it works nice. I did some extra functions for encrypt/decrypt asociated models manually thought.
    In case you use this behavior too, take in mind that as configured above, the fields in the database will have to be a little larger than twice as before, otherwise the decryption will mess up the later characters of your string.
    • Ferco posted on 02/26/11 04:23:31 PM
      [quote] I implemented this behavior in my proyect with h2osoft modifications and it works nice. I did some extra functions for encrypt/decrypt asociated models manually thought.
      In case you use this behavior too, take in mind that as configured above, the fields in the database will have to be a little larger than twice as before, otherwise the decryption will mess up the later characters of your string.
      [end quote]
      I checked in detail, and the 3des is a block cipher, so it encrypts in blocks. So, for everything to go fine, you need to do the next math (mysql, varchar, utf-8):
      new_length = 8 * (round(old_length/8) + 1)

      if you had a varchar(20), 20/8 is 2.5, rounded is 3, you add 1, you have 4*8 = 32. That's your new varchar length.
  • h2osoft posted on 04/20/10 07:54:57 AM
    Here's how We fixed:

    bootstrap.php

    <?php
        Configure
    ::write('Cryptable.cipher''tripledes');
        
    Configure::write('Cryptable.key','yoursupersecretkey');
        
    $td mcrypt_module_open('tripledes''''ecb''');
        
    $iv mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        
    mcrypt_module_close($td);
        
    Configure::write('Cryptable.iv'$iv);
    ?>

    Behavior (changed encrypt / decrypt functions)


    <?php
    public function encrypt($data) {
        if (
    $data !== '') {
            
    $td mcrypt_module_open('tripledes''''ecb''');
            
    mcrypt_generic_init($tdConfigure::read('Cryptable.key'), Configure::read('Cryptable.iv'));
            
    $encrypted_data mcrypt_generic($td$data);
            
    mcrypt_generic_deinit($td);
            
    mcrypt_module_close($td);
            return 
    base64_encode($encrypted_data);
        } else {
            return 
    '';
        }
    }

    public function 
    decrypt($data$data2 null) {
        if (
    is_object($data)) {
            unset(
    $data);
            
    $data $data2;
        }
        
        if (
    $data != '') {
            
    $td mcrypt_module_open(Configure::read('Cryptable.cipher'), '''ecb''');
            
    mcrypt_generic_init($tdConfigure::read('Cryptable.key'), Configure::read('Cryptable.iv'));
            
    $decrypted_data mdecrypt_generic($tdbase64_decode($data));
            
    mcrypt_generic_deinit($td);
            
    mcrypt_module_close($td);
            return 
    trim($decrypted_data);
        } else {
            return 
    '';
        }
    }
    ?>
  • jperras posted on 08/01/09 08:59:37 PM
    Nice job.
login to post a comment.