-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial version of the cookie encryption strategy.
- Loading branch information
Showing
2 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,200 @@ | |||
<?php | |||
/** | |||
* Lithium: the most rad php framework | |||
* | |||
* @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) | |||
* @license http://opensource.org/licenses/bsd-license.php The BSD License | |||
*/ | |||
|
|||
namespace lithium\storage\session\strategy; | |||
|
|||
use lithium\core\ConfigException; | |||
|
|||
/** | |||
* This strategy allows you to encrypt your `Session` and / or `Cookie` data so that it | |||
* is not stored in cleartext on the client side. | |||
* | |||
* Example configuration: | |||
* | |||
* {{{ | |||
* Session::config(array('default' => array( | |||
* 'adapter' => 'Cookie', | |||
* 'strategies' => array('Encrypt' => array('secret' => 'foobar')) | |||
* ))); | |||
* }}} | |||
* | |||
* By default, this strategy uses the AES algorithm in the CBC mode. You can override this | |||
* defaults by passing a different `cipher` and/or `mode` to the config like this: | |||
* | |||
* {{{ | |||
* Session::config(array('default' => array( | |||
* 'adapter' => 'Cookie', | |||
* 'strategies' => array('Encrypt' => array( | |||
* 'cipher' => MCRYPT_RIJNDAEL_128, | |||
* 'mode' => MCRYPT_MODE_ECB, | |||
* 'secret' => 'foobar' | |||
* )) | |||
* ))); | |||
* }}} | |||
* | |||
* @link http://www.php.net/manual/en/mcrypt.ciphers.php List of supported ciphers. | |||
* @link http://www.php.net/manual/en/mcrypt.constants.php List of supported modes. | |||
*/ | |||
class Encrypt extends \lithium\core\Object { | |||
|
|||
/** | |||
* Holds the initialization vector. | |||
*/ | |||
protected static $_vector = null; | |||
|
|||
/** | |||
* Constructor. | |||
* | |||
* @param array $config Configuration array. You can override the default cipher and mode. | |||
*/ | |||
public function __construct(array $config = array()) { | |||
if (!isset($config['secret'])) { | |||
throw new ConfigException("Encrypt strategy requires a secret key."); | |||
} | |||
$defaults = array( | |||
'cipher' => MCRYPT_RIJNDAEL_256, | |||
'mode' => MCRYPT_MODE_CBC | |||
); | |||
parent::__construct($config + $defaults); | |||
$this->_config['vector'] = static::_vector($this->_config['cipher'], $this->_config['mode']); | |||
} | |||
|
|||
/** | |||
* Read encryption method. | |||
* | |||
* @param | |||
* @param | |||
* @return | |||
*/ | |||
public function read($data, array $options = array()) { | |||
$class = $options['class']; | |||
|
|||
$encrypted = $class::read(null, array('strategies' => false)); | |||
|
|||
if (!isset($encrypted['__encrypted']) || !$encrypted['__encrypted']) { | |||
return isset($encrypted[$data]) ? $encrypted[$data] : null; | |||
} | |||
|
|||
$current = $this->_decrypt($encrypted['__encrypted']); | |||
|
|||
if($data) { | |||
return isset($current[$data]) ? $current[$data] : null; | |||
} else { | |||
return $current; | |||
} | |||
} | |||
|
|||
/** | |||
* Write encryption method. | |||
* | |||
* @param | |||
* @param | |||
* @return | |||
*/ | |||
public function write($data, array $options = array()) { | |||
$class = $options['class']; | |||
|
|||
$futureData = $this->read(null, $options) ?: array(); | |||
$futureData = array($options['key'] => $data) + $futureData; | |||
|
|||
$payload = empty($futureData) ? null : $this->_encrypt($futureData); | |||
|
|||
$class::write('__encrypted', $payload, array('strategies' => false) + $options); | |||
return $data; | |||
} | |||
|
|||
/** | |||
* Delete encryption method. | |||
* | |||
* @param | |||
* @param | |||
* @return | |||
*/ | |||
public function delete($data, array $options = array()) { | |||
$class = $options['class']; | |||
|
|||
$futureData = $this->read(null, $options) ?: array(); | |||
unset($futureData[$options['key']]); | |||
|
|||
$payload = empty($futureData) ? null : $this->_encrypt($futureData); | |||
|
|||
$class::write('__encrypted', $payload, array('strategies' => false) + $options); | |||
return $data; | |||
} | |||
|
|||
/** | |||
* Determines if the Mcrypt extension has been installed. | |||
* | |||
* @return boolean `true` if enabled, `false` otherwise | |||
*/ | |||
public static function enabled() { | |||
return extension_loaded('mcrypt'); | |||
} | |||
|
|||
/** | |||
* Serialize and encrypt a given data array. | |||
* | |||
* @param | |||
* @return | |||
*/ | |||
protected function _encrypt($decrypted = array()) { | |||
extract($this->_config); | |||
|
|||
$encrypted = mcrypt_encrypt($cipher, $secret, serialize($decrypted), $mode, $vector); | |||
$data = base64_encode($encrypted) . base64_encode($vector); | |||
|
|||
return $data; | |||
} | |||
|
|||
/** | |||
* Decrypt and unserialize a previously encrypted string. | |||
* | |||
* @param | |||
* @return | |||
*/ | |||
protected function _decrypt($encrypted) { | |||
extract($this->_config); | |||
|
|||
$vectorSize = strlen(base64_encode(str_repeat(" ", static::_vectorSize($cipher, $mode)))); | |||
$vector = base64_decode(substr($encrypted, -$vectorSize)); | |||
$data = base64_decode(substr($encrypted, 0, -$vectorSize)); | |||
|
|||
$decrypted = mcrypt_decrypt($cipher, $secret, $data, $mode, $vector); | |||
$data = unserialize(trim($decrypted)); | |||
|
|||
return $data; | |||
} | |||
|
|||
/** | |||
* Generates an initialization vector. | |||
* | |||
* @param | |||
* @param | |||
* @return string Returns an initialization vector. | |||
* @link http://www.php.net/manual/en/function.mcrypt-create-iv.php | |||
*/ | |||
protected static function _vector($cipher, $mode) { | |||
if(static::$_vector) { | |||
return static::$_vector; | |||
} | |||
|
|||
$size = static::_vectorSize($cipher, $mode); | |||
return static::$_vector = mcrypt_create_iv($size, MCRYPT_DEV_URANDOM); | |||
} | |||
|
|||
/** | |||
* Returns the vector size vor a given cipher and mode. | |||
* | |||
* @param | |||
* @param | |||
* @return | |||
*/ | |||
protected static function _vectorSize($cipher, $mode) { | |||
return mcrypt_get_iv_size($cipher, $mode); | |||
} | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,99 @@ | |||
<?php | |||
/** | |||
* Lithium: the most rad php framework | |||
* | |||
* @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) | |||
* @license http://opensource.org/licenses/bsd-license.php The BSD License | |||
*/ | |||
|
|||
namespace lithium\tests\cases\storage\session\strategy; | |||
|
|||
use lithium\storage\session\strategy\Encrypt; | |||
use lithium\tests\mocks\storage\session\strategy\MockCookieSession; | |||
|
|||
class EncryptTest extends \lithium\test\Unit { | |||
|
|||
public $secret = 'foobar'; | |||
|
|||
/** | |||
* Skip the test if Mcrypt extension is unavailable. | |||
* | |||
* @return void | |||
*/ | |||
public function skip() { | |||
$this->skipIf(!Encrypt::enabled(), 'The Mcrypt extension is not installed or enabled.'); | |||
} | |||
|
|||
public function setUp() { | |||
$this->mock = 'lithium\tests\mocks\storage\session\strategy\MockCookieSession'; | |||
MockCookieSession::reset(); | |||
} | |||
|
|||
public function testConstructException() { | |||
$this->expectException('/Encrypt strategy requires a secret key./'); | |||
$encrypt = new Encrypt(); | |||
} | |||
|
|||
public function testEnabled() { | |||
$this->assertTrue(Encrypt::enabled()); | |||
} | |||
|
|||
public function testConstruct() { | |||
$encrypt = new Encrypt(array('secret' => $this->secret)); | |||
$this->assertTrue($encrypt instanceof Encrypt); | |||
} | |||
|
|||
public function testWrite() { | |||
$encrypt = new Encrypt(array('secret' => $this->secret)); | |||
|
|||
$key = 'fookey'; | |||
$value = 'barvalue'; | |||
|
|||
$result = $encrypt->write($value, array('class' => $this->mock, 'key' => $key)); | |||
$cookie = MockCookieSession::data(); | |||
|
|||
$this->assertTrue($result); | |||
$this->assertTrue($cookie['__encrypted']); | |||
$this->assertTrue(is_string($cookie['__encrypted'])); | |||
$this->assertNotEqual($cookie['__encrypted'], $value); | |||
} | |||
|
|||
public function testRead() { | |||
$encrypt = new Encrypt(array('secret' => $this->secret)); | |||
|
|||
$key = 'fookey'; | |||
$value = 'barvalue'; | |||
|
|||
$result = $encrypt->write($value, array('class' => $this->mock, 'key' => $key)); | |||
$this->assertTrue($result); | |||
|
|||
$cookie = MockCookieSession::data(); | |||
$result = $encrypt->read($key, array('class' => $this->mock)); | |||
|
|||
$this->assertEqual($value, $result); | |||
$this->assertNotEqual($cookie['__encrypted'], $result); | |||
} | |||
|
|||
public function testDelete() { | |||
$encrypt = new Encrypt(array('secret' => $this->secret)); | |||
|
|||
$key = 'fookey'; | |||
$value = 'barvalue'; | |||
|
|||
$result = $encrypt->write($value, array('class' => $this->mock, 'key' => $key)); | |||
$this->assertTrue($result); | |||
|
|||
$cookie = MockCookieSession::data(); | |||
$result = $encrypt->read($key, array('class' => $this->mock)); | |||
|
|||
$this->assertEqual($value, $result); | |||
|
|||
$result = $encrypt->delete($key, array('class' => $this->mock, 'key' => $key)); | |||
|
|||
$cookie = MockCookieSession::data(); | |||
$this->assertTrue(empty($cookie['__encrypted'])); | |||
|
|||
$result = $encrypt->read($key, array('class' => $this->mock)); | |||
$this->assertFalse($result); | |||
} | |||
} |
8f20cb5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi @daschl, great work ! It looks awesome.
Do you think we can re-use this Encrypt class to encrypt/decrypt data like an email field stored in a database ?
8f20cb5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kamul545 Thanks!
Actually we thought about doing refactoring it into a more general class later if there's the need for it. As it looks like it is, so let's get this class on track first and then open an enhancement request that discusses this - ok?
Regards
8f20cb5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work. Looks good to me, on a cursory inspection.
I wouldn't worry too much about refactoring to be more general - that's something that can be done at a later date without BC break, if done correctly.
8f20cb5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daschl Of course no problem, that was not a request, just a simple question by curiosity :P