<?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);
}
}
|