Encrypt session data in PHP
Posted: February 1st, 2011 | Author: enrico | Filed under: Cryptography | 20 Comments »As promised in my last post I present an example of strong cryptography in PHP to secure session data.
This is a very simple implementation that can be used to improve the security of PHP applications especially in shared environments where different users have access to the same resources. As you know, the PHP session data are managed by default using temporary files. In shared environment a malicious user that is able to access to these temporary files can easly read the session data because they are stored in plaintext (data in a session file is theserialization of the array $_SESSION).
In theory, the session data should be stored in folders that are accessible only by the owner of the web site, but never say never (btw, you can manage the location of the session data using the session_save_path function or changing the session.save_path in the php.ini).
To secure the session data I used strong cryptography to encrypt the content using the mcrypt extension of PHP. I chosen the simmetric cipher Rijandel-256 to encrypt the session data and the openssl_random_pseudo_bytes() function to generate a random key of 256 bit.
The idea is to use a cookie variable to store the key that will be used to encrypt the session data. In this way the key is stored only in the client (the browser) and only the client is able to decrypt the session data on the server. Each time we encrypt the session data we re-generate the IV vector in a random way using the mcrypt_create_iv() function. It is very important to generate a unique IV in each encryption. This best practice increase the security of the encryption algorithm.
It’s important to note that this implementation is not secure against session hijacking attack. If someone is able to capture the cookie variable of a client and have access to the temporary session files, in the server, he/she will be able to decrypt the session data. Our goal is to protect session data against attacks on shared environments.
The idea to encrypt the session data is not new, for instance Chris Shiflett proposed an implementation in his book “Essential PHP Security” (O’Reilly, 2006). Shiflett used a $_SERVER variable to store the key used to encrypt the session data. Kevin Schroeder, my colleague at Zend Technologies, implemented a very similar session encryption algorithm extending the Zend_Session class of Zend Framework (you can find it here). In my solution, I used some of the best practices related to strong cryptography to implement a secure session handler.
Below the source code of my implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | /** * ------------------------------------------------ * Encrypt PHP session data using files * ------------------------------------------------ * The encryption is built using mcrypt extension * and the randomness is managed by openssl * * @author Enrico Zimuel (enrico@zimuel.it) * @copyright GNU General Public License */ class SecureSession { const CIPHER= MCRYPT_RIJNDAEL_256; const CIPHER_MODE= MCRYPT_MODE_CBC; /** * Key for encryption/decryption * * @var string */ private static $_key; /** * Path of the session file * * @var string */ private static $_path; /** * Session name (optional) * * @var string */ private static $_name; /** * Size of the IV vector for encryption * * @var integer */ private static $_ivSize; /** * Cookie variable name of the key * * @var string */ private static $_keyName; /** * Generate a random key * fallback to mt_rand if PHP < 5.3 or no openssl available * * @param integer $length * @return string */ private static function _randomKey($length=32) { if(function_exists('openssl_random_pseudo_bytes')) { $rnd = openssl_random_pseudo_bytes($length, $strong); if($strong === TRUE) return $rnd; } for ($i=0;$i<$length;$i++) { $sha= sha1(mt_rand()); $char= mt_rand(0,30); $rnd.= chr(hexdec($sha[$char].$sha[$char+1])); } return $rnd; } /** * Open the session * * @param string $save_path * @param string $session_name * @return bool */ public static function open($save_path, $session_name) { self::$_path= $save_path.'/'; self::$_name= $session_name; self::$_keyName= "KEY_$session_name"; self::$_ivSize= mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE); if (empty($_COOKIE[self::$_keyName])) { $keyLength= mcrypt_get_key_size(self::CIPHER, self::CIPHER_MODE); self::$_key= self::_randomKey($keyLength); $cookie_param = session_get_cookie_params(); setcookie( self::$_keyName, base64_encode(self::$_key), $cookie_param['lifetime'], $cookie_param['path'], $cookie_param['domain'], $cookie_param['secure'], $cookie_param['httponly'] ); } else { self::$_key= base64_decode($_COOKIE[self::$_keyName]); } return true; } /** * Close the session * * @return bool */ public static function close() { return true; } /** * Read and decrypt the session * * @param integer $id * @return string */ public static function read($id) { $sess_file = self::$_path.self::$_name."_$id"; $data= @file_get_contents($sess_file); if (empty($data)) { return false; } $iv= substr($data,0,self::$_ivSize); $encrypted= substr($data,self::$_ivSize); $decrypt = mcrypt_decrypt( self::CIPHER, self::$_key, $encrypted, self::CIPHER_MODE, $iv ); return rtrim($decrypt, "�"); } /** * Encrypt and write the session * * @param integer $id * @param string $data * @return bool */ public static function write($id, $data) { $sess_file = self::$_path.self::$_name."_$id"; $iv= mcrypt_create_iv(self::$_ivSize, MCRYPT_RAND); if ($fp = @fopen($sess_file, "w")) { $encrypted= mcrypt_encrypt( self::CIPHER, self::$_key, $data, self::CIPHER_MODE, $iv ); $return = fwrite($fp, $iv.$encrypted); fclose($fp); return $return; } else { return false; } } /** * Destroy the session * * @param int $id * @return bool */ public static function destroy($id) { $sess_file = self::$_path.self::$_name."_$id"; setcookie (self::$_keyName, '', time() - 3600); return(@unlink($sess_file)); } /** * Garbage Collector * * @param int $max * @return bool */ public static function gc($max) { foreach (glob(self::$_path.self::$_name.'_*') as $filename) { if (filemtime($filename) + $max < time()) { @unlink($filename); } } return true; } } // Set the custom PHP session handler ini_set('session.save_handler', 'user'); session_set_save_handler(array('SecureSession', 'open'), array('SecureSession', 'close'), array('SecureSession', 'read'), array('SecureSession', 'write'), array('SecureSession', 'destroy'), array('SecureSession', 'gc') ); |
You can donwload the SecureSession class here.
In order to use the SecureSession class you have to include it in your PHP project before the session_start function.
After that you can use the PHP session as usual.







Any application that used this implementation to encrypt session data and store it in cookies would likely find that attackers could decrypt messages without knowing the key, and that attackers could trivially rewrite the contents of their own session.
Hi Tomas,
I didn’t store encryption session data in a cookie. I use cookie only to store the key. The session data are stored in a file into the server. Can you explain what is wrong with this implementation?
Tomas, you confused this with code someone will actually use.
Actually, this works great for encrypting the session files on the server, which sometimes may store your web site user’s passwords. I’ve just updated it to work with PHP < 5.2. Thanks for sharing, Enrico!
Hi,
Great class. Gonna apply it to the Zend Framework Zend_Session_SaveHandler_DbTable. This way I’ll have a session solution that’s usable in a loadbalanced environment even without a central NAS or syncing sessions over multiple servers.
Hi,
Great class that i’m using little bit modified.
As my session handler was memcached, i rewrite read write files parts using memcached server to store the sessions files.
Don’t seems to overload server process to crypt/decrypt datas.
Just for the case that someone is having the same problems as I had with this class. I had to add:
register_shutdown_function(‘session_write_close’);
otherwise I often had error messages like this:
Warning: Invalid callback SecureSession::write, class ‘SecureSession’ not found in Unknown on line 0 … (and so on)
HTH someone. Thanks for the class Enrico.
For me this works great for encrypting the session files on the server
I’ve received this warning:
Warning: ini_set() [function.ini-set]: A session is active. You cannot change the session module’s ini settings at this time in D:devWebfuncoesSecureSession.php on line 175
Hi Enrico, I can’t seem to make it work using the following code:
I’m getting the following error:
Fatal error: session_start() [function.session-start]: Failed to initialize storage module: user (path: ./sessions) in …
Hi Enrico,
Im also getting the
Fatal error: session_start() [function.session-start]: Failed to initialize storage module: user (path: ./sessions)
Hi,
Great article and thanks for the class, works great for me.
Have to agree this is a great class. It works perfectly well for me.
Hi thanks for this class. I just found out I should be encrypting session variables and this is a one-click solution. Also does what I wanted to do which was encrypt session data using key held at the client.
The guys commenting at the top about trivial decoding etc – what are they on?
Hi Mike,
the first comment of Thomas was related to a misunderstanding. In this implementation I don’t store session data in cookie. The session data are stored server side as usual. The cookie is used to store only the encryption and authentication key.
No errors or warnings, but there are also no session files written in the temporary folder. Usally temp files have a prefix like ‘sess_’.
So, I don’t understand how this class will work …
Did you try the demo.php script? It works storing the session file in the /tmp folder, for GNU/Linux environment. You can find the demo.php in the github repository of the project: https://github.com/ezimuel/PHP-Secure-Session
Hi Enrico,
I’ve made a mistake. In my script I still had to make the following:
// set custom session path
is_dir($sessdir = $conf['dir']['tmp'] . ‘/session’) or @mkdir($sessdir);
(isset ($sessdir) && is_dir($sessdir)) and session_save_path(realpath($sessdir));
Now it works like a charm
Thanks a lot for this helpful class.
Just to clarify the only purpose of this script is to protect the session files from being seen in plain text (i.e., by a rouge server admin or if /tmp directory is accessed by other users)? And, this does not protect from session hi-jacking/fixation? Kindly clarify. Thank you.
Yes, the purpose is to protect the PHP session files from plaintext to ciphertext. In this way all the users of the operating system are not able to read the session content.
To protect the PHP application against session hijacking or session fixation you should check the source of the client, on each call, and re-generate the session id often.