"The human factor is truly security's weakest link" Kevin D. Mitnick

Encrypt session data in PHP

Posted: February 1st, 2011 | Author: | 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.


20 Comments on “Encrypt session data in PHP”

  1. 1 Thomas H. Ptacek said on February 1st, 2011:

    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.

  2. 2 Enrico Zimuel said on February 1st, 2011:

    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?

  3. 3 Nate said on February 2nd, 2011:

    Tomas, you confused this with code someone will actually use.

  4. 4 Dave said on March 10th, 2011:

    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!

  5. 5 René said on March 27th, 2011:

    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.

  6. 6 Jerome said on April 17th, 2011:

    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.

  7. 7 Andre said on June 4th, 2011:

    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.

  8. 8 web forms said on June 17th, 2011:

    For me this works great for encrypting the session files on the server

  9. 9 Andre said on July 11th, 2011:

    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

  10. 10 Francois said on August 3rd, 2011:

    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 …

  11. 11 Paula said on August 30th, 2011:

    Hi Enrico,
    Im also getting the
    Fatal error: session_start() [function.session-start]: Failed to initialize storage module: user (path: ./sessions)

  12. 12 Postie said on September 6th, 2011:

    Hi,

    Great article and thanks for the class, works great for me.

  13. 13 Karen Hobbs said on November 8th, 2011:

    Have to agree this is a great class. It works perfectly well for me. :)

  14. 14 Mike Thompson said on June 9th, 2012:

    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?

  15. 15 enrico said on June 19th, 2012:

    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.

  16. 16 Planlos said on June 21st, 2012:

    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 …

  17. 17 enrico said on June 24th, 2012:

    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

  18. 18 Planlos said on June 30th, 2012:

    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.

  19. 19 Zach said on August 19th, 2012:

    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.

  20. 20 enrico said on October 11th, 2012:

    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.


Leave a Reply

  • Spam Protection by WP-SpamFree