a tiny mvc framework for php using php-activerecord
at master 187 lines 5.0 kB view raw
1<?php 2/* 3 * secure cookie-based session storage, based on the EncryptedCookieStore rails 4 * plugin (http://github.com/FooBarWidget/encrypted_cookie_store) 5 * 6 * Copyright (c) 2010 joshua stein <jcs@jcs.org> 7 * 8 * process of storing session data with a given key: 9 * 1. create a random IV 10 * 2. encrypt the IV with the key in 128-bit AES in ECB mode 11 * 3. create a SHA1 HMAC (with the key) of the session data 12 * 4. encrypt the HMAC and session data together with the key in 256-bit AES in 13 * CFB mode 14 * 5. store the base64-encoded encrypted IV and encrypted HMAC+data as a cookie 15 * 16 * to read the encrypted data on the next visit: 17 * 1. base64-decode the IV and data 18 * 2. decrypt the IV with the key 19 * 3. decrypt the HMAC+data with the key and decrypted IV 20 * 4. verify that the HMAC of the decrypted data matches the decrypted HMAC 21 * 5. return the plaintext session data 22 * 23 */ 24 25namespace HalfMoon; 26 27class EncryptedCookieSessionStore { 28 /* the most amount of data we can store in the cookie (post-encryption) */ 29 public static $MAX_COOKIE_LENGTH = 4096; 30 31 /* cookie parameters */ 32 private static $settings = array(); 33 34 private $cookie_name = ""; 35 private $key = null; 36 37 public function __construct($key) { 38 if (!function_exists("mcrypt_encrypt")) 39 throw new \HalfMoon\HalfMoonException("mcrypt extension not " 40 . "installed"); 41 if (strlen($key) != 32) 42 throw new \HalfMoon\HalfMoonException("cookie encryption key must " 43 . "be 32 characters long"); 44 45 /* disable php's own sending of session cookies since they will 46 * conflict with what we're generating here */ 47 ini_set("session.use_cookies", "off"); 48 49 /* load settings as they are from boot, since controllers may change 50 * them */ 51 static::$settings = session_get_cookie_params(); 52 53 $this->key = pack("H*", $key); 54 } 55 56 public static function set_lifetime($secs) { 57 static::$settings["lifetime"] = $secs; 58 } 59 60 public static function set_path($path) { 61 static::$settings["path"] = $path; 62 } 63 64 public static function set_domain($domain) { 65 static::$settings["domain"] = $domain; 66 } 67 68 public static function set_secure($secure) { 69 static::$settings["secure"] = $secure; 70 } 71 72 public static function set_httponly($httponly) { 73 static::$settings["httponly"] = $httponly; 74 } 75 76 public function open($savepath, $name) { 77 $this->cookie_name = $name; 78 79 return true; 80 } 81 82 public function read($id) { 83 if (!isset($_COOKIE[$this->cookie_name])) 84 return ""; 85 86 if ($_COOKIE[$this->cookie_name] == "") 87 return ""; 88 89 list($e_iv, $e_data) = explode("--", $_COOKIE[$this->cookie_name], 2); 90 91 if (strlen($e_iv) && strlen($e_data)) { 92 $e_iv = base64_decode($e_iv); 93 $e_data = base64_decode($e_data); 94 95 $iv = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->key, $e_iv, 96 MCRYPT_MODE_ECB); 97 98 $data_and_hmac = @mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->key, 99 $e_data, MCRYPT_MODE_CFB, $iv); 100 101 $pieces = explode("--", $data_and_hmac, 2); 102 103 if (count($pieces) == 2) { 104 list($hmac, $data) = $pieces; 105 106 $hmac = base64_decode($hmac); 107 108 if (!strlen($hmac)) 109 throw new \HalfMoon\InvalidCookieData("no HMAC"); 110 111 if (hash_hmac("sha1", $data, $this->key, $raw = true) === $hmac) 112 return $data; 113 else 114 throw new \HalfMoon\InvalidCookieData("invalid HMAC"); 115 } 116 } 117 118 return ""; 119 } 120 121 public function write($id, $data) { 122 if (headers_sent()) 123 return false; 124 125 /* generate random iv for aes-256-cfb */ 126 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, 127 MCRYPT_MODE_CFB), MCRYPT_RAND); 128 129 /* encrypt the iv with aes-128-ecb */ 130 $e_iv = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->key, $iv, 131 MCRYPT_MODE_ECB); 132 133 $hmac = hash_hmac("sha1", $data, $this->key, $raw_output = true); 134 135 /* encrypt the hmac and data with aes-256-cfb, using the random iv */ 136 $e_data = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, 137 base64_encode($hmac) . "--" . $data, MCRYPT_MODE_CFB, $iv); 138 139 $cookie = base64_encode($e_iv) . "--" . base64_encode($e_data); 140 141 if (strlen($cookie) > \HalfMoon\EncryptedCookieSessionStore::$MAX_COOKIE_LENGTH) 142 throw new \HalfMoon\InvalidCookieData("cookie data too long (" 143 . strlen($cookie) . " > " 144 . \HalfMoon\EncryptedCookieSessionStore::$MAX_COOKIE_LENGTH . ")"); 145 146 setcookie( 147 $this->cookie_name, 148 $cookie, 149 (static::$settings["lifetime"] ? 150 time() + static::$settings["lifetime"] : 0), 151 static::$settings["path"], 152 static::$settings["domain"], 153 static::$settings["secure"], 154 static::$settings["httponly"] 155 ); 156 157 /* just to help in debugging */ 158 $_COOKIE[$this->cookie_name] = $cookie; 159 160 return true; 161 } 162 163 public function destroy($id) { 164 @setcookie( 165 $this->cookie_name, 166 "", 167 (static::$settings["lifetime"] ? 168 time() + $settings["lifetime"] : 0), 169 static::$settings["path"], 170 static::$settings["domain"], 171 static::$settings["secure"], 172 static::$settings["httponly"] 173 ); 174 175 return true; 176 } 177 178 public function gc($maxlife) { 179 return true; 180 } 181 182 public function close() { 183 return true; 184 } 185} 186 187?>