| 
<?php
 /**
 * Encrypting PHP session handler
 * The session data is encypted using a random key stored in a cookie
 *
 * Note that since there is no session handler hook for regenerateId in PHP < 5.5.10, we can't tell
 * when we should create a new key - and in the absence of further checks, the key
 * is vulnerable to fixation!
 *
 * The actual encryption is provided by a slaved object - the example below uses AES256
 * The session object provides key management and stackable session handling capability
 * while the encryptor deals with encoding, encryption algorithms/modes/initialization
 * vectors.
 *
 * Here I've used the session id as the initialization vector. This is marginally better
 * than just encrypting the data, but is predictable. OTOH using a random IV means that
 * we have an additional complication of managing that data.
 */
 
 class encryptingSessionHandler extends stackSess {
 private $ekey;
 private $keyname;
 private $engine;
 private $sessionidread;
 private $keyregenerated;
 
 function __construct($shNext=false, $keyname=false)
 {
 if (false!=$shNext) {
 $this->addNext($shNext);
 }
 if ($keyname) {
 $this->keyname=$keyname;
 } else {
 $this->keyname='x' .session_name();
 }
 }
 function open($save_path, $name)
 {
 $this->logit("encryptingSessionHandler::open($save_path, $name)");
 $this->engine=new encryptorEngine();
 
 $p=session_get_cookie_params();
 
 if (isset($_COOKIE[$this->keyname]) && 5<strlen($_COOKIE[$this->keyname])) {
 $this->ekey=urldecode($_COOKIE[$this->keyname]);
 } else {
 $this->createKey();
 $this->logit("changed key to " . $this->ekey);
 }
 if ($this->shStackNext) {
 $result=$this->shStackNext->open($save_path, $name);
 }
 return ($this->ekey && $result);
 }
 
 function read($session_id)
 {
 $this->logit("encryptingSessionHandler::read($session_id)");
 $this->sessionidread=$session_id;
 if ($this->shStackNext) {
 $data=$this->shStackNext->read($session_id);
 if (!$data) return '';
 return $this->engine->decrypt($data,$this->ekey, $session_id);
 } else {
 trigger_error("Encrpyting ssession handler must be chained to a storage handler", E_USER_ERROR);
 return false;
 }
 }
 
 function write($session_id, $session_data)
 {
 $this->logit("encryptingSessionHandler::write($session_id, session_data)");
 if ($this->sessionidread!=$session_id && !$this->keyregenerated) {
 $msg=$this->sessionidread . " to " . $session_id;
 trigger_error("Session ID regenerated ($msg) but encryption key not changed!", E_USER_WARNING);
 }
 $data=$this->engine->encrypt($session_data,$this->ekey,$session_id);
 return $this->shStackNext->write($session_id, $data);
 }
 
 /**
 * in addition to telling the other handlers to destroy the session
 * we also remove the key
 */
 function destroy($session_id)
 {
 $this->createKey();
 return $this->shStackNext->destroy($session_id);
 }
 
 function create_sid($newltCreatedSid=false)
 {
 $this->logit("encryptingSessionHandler::create_sid()");
 $this->createKey();
 $newlyCreatedSid=$this->shStackNext->create_sid($newlyCreatedSid);
 return $newlyCreatedSid;
 }
 
 function createKey()
 {
 $this->logit("encryptingSessionHandler::createKey()");
 $p=session_get_cookie_params();
 $key=$this->engine->generateRandomKey();
 if (setcookie($this->keyname, $key,
 $p['lifetime'], $p['path'], $p['domain'], $p['secure'], $p['httponly'])) {
 $this->keyregenerated=true;
 $this->ekey=$key;
 return $key;
 }
 $this->logit("Failed in encryptingSessionHandler::createKey()");
 return false;
 }
 }
 
 class dummyEncryptorEngine {
 function encrypt($data,$key,$sessionid)
 {
 return $data;
 }
 function decrypt($data,$key,$sessionid)
 {
 return $data;
 }
 function generateRandomKey()
 {
 return uniqid();
 }
 }
 class encryptorEngine {
 private $descriptor;
 private $iv;
 private $iv_basedon;
 
 function __construct($algo='rijndael-256')
 {
 $this->d=mcrypt_module_open($algo, '', 'cbc', '');
 }
 function checkIv($sessionid)
 {
 if ($sessionid==$this->iv_basedon) {
 return $this->iv;
 }
 $this->iv_basedon=$sessionid;
 
 $proto=$sessionid;
 $ivlength=mcrypt_enc_get_iv_size($this->d);
 while (strlen($sessionid) && strlen($proto)<$ivlength) {
 $proto.=$sessionid;
 }
 $proto=sha1($sessionid);
 $proto.=$proto;
 $this->iv=substr($proto, 0, $ivlength);
 return $this->iv;
 }
 function encrypt($data,$key,$sessionid)
 {
 $iv=$this->checkIv($sessionid);
 mcrypt_generic_init($this->d, $this->unmakesafe($key), $iv);
 $encrypted=base64_encode(mcrypt_generic($this->d, $data));
 mcrypt_generic_deinit($this->d);
 return $encrypted;
 }
 function decrypt($data,$key,$sessionid)
 {
 $iv=$this->checkIv($sessionid);
 mcrypt_generic_init($this->d, $this->unmakesafe($key), $iv);
 $decrypted=mdecrypt_generic($this->d, base64_decode($data));
 mcrypt_generic_deinit($this->d);
 return $decrypted;
 }
 function generateRandomKey()
 {
 $length=mcrypt_enc_get_key_size($this->d);
 if (function_exists('openssl_random_pseudo_bytes')) {
 return $this->makesafe(openssl_random_pseudo_bytes($length));
 }
 if (function_exists('random_bytes')) {
 return $this->makesafe(random_bytes($length));
 }
 $key=$this->makesafe(uniqid('',true));
 trigger_error("Session encryption using low entropy key generator", E_USER_WARNING);
 return $key;
 }
 function makesafe($str)
 {
 $str=base64_encode($str);
 $str=str_replace('+','-',$str);
 $str=str_replace('=','.',$str);
 return $str;
 }
 function unmakesafe($str)
 {
 $str=str_replace('.','=',$str);
 $str=str_replace('-','+',$str);
 return base64_decode($str);
 }
 }
 
 |