Posted By

mladoux on 06/20/11


Tagged

cookie security Cryptography


Versions (?)

Cookie Class


 / Published in: PHP
 

HMAC cookie implementation based on BigOrNot_CookieManager by Mattieu Huguet. (http://bigornot.blogspot.com/2008/06/securing-cookies-php-implementation.html) Rewritten for clarity, updated for use with PHP 5 and Zend Framework dependencies removed by Mark A. LaDoux.

  1. <?php
  2. /******************************************************************************
  3.   Cookie Class
  4.  
  5.   HMAC cookie implementation based on BigOrNot_CookieManager by Mattieu Huguet.
  6.   (http://bigornot.blogspot.com/2008/06/securing-cookies-php-implementation.html)
  7.   Rewritten for clarity and updated for use with PHP 5 by Mark A. LaDoux.
  8. /******************************************************************************/
  9.  
  10. class Cookie
  11. {
  12. /**************************************************************************
  13.   Settings
  14.   /**************************************************************************/
  15.  
  16. protected static $key = null; // Server secret key
  17.  
  18. protected static $calg = MCRYPT_RIJNDAEL_256; // Cryptographic algorithm
  19. // used to encrypt cookie
  20. // data
  21.  
  22. protected static $mode = MCRYPT_MODE_CBC; // Cryptographic mode
  23.  
  24. protected static $cmod = null; // mcrypt module resource
  25.  
  26. protected static $hi_c = true; // High confidentiality mode
  27. // ( whether or not our
  28. // cookie data is
  29. // encrypted. )
  30.  
  31. protected static $ssl = false; // SSL support
  32.  
  33. protected static $halg = 'sha512'; // Hash algorithm for HMAC
  34.  
  35. /**************************************************************************
  36.   Constructor
  37.  
  38.   @access public
  39.   @param array $config Settings for the cookie class
  40.   @return null Function does not return a result
  41.   /**************************************************************************/
  42.  
  43. public function __construct($config = null)
  44. {
  45. // Check config format
  46.  
  47. if(! is_array($config))
  48. die('CLASS ERROR (Cookie): Config must be an array!');
  49.  
  50. // REQUIRED SETTING
  51.  
  52. if(! isset($config['key']))
  53. die('CLASS ERROR (Cookie): Secret key must be set!');
  54. self::$key = $config['key'];
  55.  
  56. // OPTIONAL SETTINGS
  57.  
  58. if(isset($config['calg'])) self::$calg = $config['calg'];
  59. if(isset($config['mode'])) self::$mode = $config['mode'];
  60. if(isset($config['hi_c'])) self::$hi_c = $config['hi_c'];
  61. if(isset($config['ssl'])) self::$ssl = $config['ssl'];
  62. if(isset($config['halg'])) self::$halg = $config['halg'];
  63.  
  64. // Load mcrypt module
  65.  
  66. self::$cmod = mcrypt_module_open(self::$calg, '', self::$mode, '');
  67. if(self::$cmod === false)
  68. die('CLASS ERROR (Cookie): Error loading mcrypt module');
  69. }
  70.  
  71. /**************************************************************************
  72.   Get High Confidentiality Mode
  73.  
  74.   Reports whether High Confidentiality Mode is enabled or disabled.
  75.  
  76.   @access public
  77.   @return bool TRUE if enabled, FALSE if not
  78.   /**************************************************************************/
  79.  
  80. public function get_hi_c()
  81. {
  82. return self::$hi_c;
  83. }
  84.  
  85. /**************************************************************************
  86.   Set High Confidentiality Mode ( Enabled by default )
  87.  
  88.   Turns cookie data encryption on and off
  89.  
  90.   @access public
  91.   @param bool $enable TRUE to enable, FALSE to disable
  92.   @return mixed returns self instance
  93.   /**************************************************************************/
  94.  
  95. public function set_hi_c($enable)
  96. {
  97. // if format is invalid, do nothing and return instance
  98.  
  99. if(! is_bool($enable)) return self;
  100.  
  101. // set value
  102.  
  103. self::$hi_c = $enable;
  104.  
  105. // return instance
  106.  
  107. return self;
  108. }
  109.  
  110. /**************************************************************************
  111.   Get SSL Suport
  112.  
  113.   Reports whether or not SSL is enabled or disabled
  114.  
  115.   @access public
  116.   @return bool TRUE if enabled, FALSE if disabled
  117.   /**************************************************************************/
  118.  
  119. public function get_ssl()
  120. {
  121. return self::$ssl;
  122. }
  123.  
  124. /**************************************************************************
  125.   Set SSL Support ( Disabled by default )
  126.  
  127.   Turns SSL support on or off.
  128.  
  129.   @access public
  130.   @param bool $enable TRUE to enable, FALSE to disable
  131.   @return mixed Returns self instance
  132.   /**************************************************************************/
  133.  
  134. public function set_ssl($enable)
  135. {
  136. // if format is invalid, do nothing and return instance
  137.  
  138. if(! is_bool($enable)) return self;
  139.  
  140. // set value
  141.  
  142. self::$ssl = $enable;
  143.  
  144. // return instance
  145.  
  146. return self;
  147. }
  148.  
  149. /**************************************************************************
  150.   Set a secure cookie
  151.  
  152.   @access public
  153.   @param string $name name of cookie
  154.   @param string $value cookie data
  155.   @param string $cid cookie ID
  156.   @param int $expire cookie TTL
  157.   @param string $path cookie path
  158.   @param string $domain cookie domain
  159.   @param bool $secure when TRUE, cookie requires an SSL connection
  160.   @param bool $httponly when TRUE, cookie is only accessable through
  161.   HTTP protocol
  162.   @return null function does not return a result
  163.   /**************************************************************************/
  164.  
  165. public function set_cookie(
  166. $name,
  167. $value,
  168. $cid,
  169. $expire = 0,
  170. $path = '/',
  171. $domain = '',
  172. $secure = false,
  173. $httponly = true
  174. ) {
  175. // secure cookie value
  176.  
  177. $secure_value = $this->secure_cookie_value($value, $cid, $expire);
  178.  
  179. // set the cookie
  180.  
  181. $this->set_classic_cookie(
  182. $name,
  183. $secure_value,
  184. $expire,
  185. $path,
  186. $domain,
  187. $secure,
  188. $httponly
  189. );
  190. }
  191.  
  192. /**************************************************************************
  193.   Delete a cookie
  194.  
  195.   @access public
  196.   @param string $name name of cookie
  197.   @param string $path cookie path
  198.   @param string $domain cookie domain
  199.   @param bool $secure When TRUE, cookies require an SSL connection
  200.   @param bool $httponly When TRUE, cookie is only accessable through
  201.   HTTP protocol
  202.   @return null function does not return a result
  203.   /**************************************************************************/
  204.  
  205. public function delete_cookie(
  206. $name,
  207. $path = '/',
  208. $domain = '',
  209. $secure = false,
  210. $httponly = true
  211. ) {
  212. // set expiration to 1980-01-01
  213.  
  214. $expire = 315554400;
  215.  
  216. // set cookie
  217.  
  218. $this->set_classic_cookie(
  219. $name,
  220. '',
  221. $expire,
  222. $path,
  223. $domain,
  224. $secure,
  225. $httponly
  226. );
  227. }
  228.  
  229. /**************************************************************************
  230.   Get secure cookie value
  231.  
  232.   Verifies the integrity of the cookie data and decrypts it.
  233.   If cookie is invalid, it can be automatically destroyed (default).
  234.  
  235.   @access public
  236.   @param string $name name of cookie
  237.   @param string $del_invalid destroy invalid cookies
  238.   @return mixed if valid, returns array, otherwise false
  239.   /**************************************************************************/
  240.  
  241. public function get_cookie_value($name, $del_invalid = true)
  242. {
  243. // check if cookie exists
  244.  
  245. if(! $this->cookie_exists($name)) return false;
  246.  
  247. // get cookie data
  248.  
  249. $values = explode('|', $_COOKIE[$name]);
  250. if((count($values) === 4) && ($values[1] == 0 || $values[1] >= time()))
  251. {
  252. // prepare cookie data
  253.  
  254. $key = hash_hmac(self::$halg, $values[0].$values[1], self::$key);
  255. $cookie_data = base64_decode($values[2]);
  256. if($this->get_hi_c()) {
  257. $data = $this->decrypt_data($cookie_data, $key, md5($values[1]));
  258. }
  259. else
  260. {
  261. $data = $cookie_data;
  262. }
  263.  
  264. // verify data
  265.  
  266. if(self::$ssl && isset($_SERVER['SSL_SESSION_ID']))
  267. {
  268. $verify_key = hash_hmac(
  269. self::$halg,
  270. $values[0].$values[1].$data.$_SERVER['SSL_SESSION_ID'],
  271. $key
  272. );
  273. }
  274. else
  275. {
  276. $verify_key = hash_hmac(
  277. self::$halg,
  278. $values[0].$values[1].$data,
  279. $key
  280. );
  281. }
  282.  
  283. if($verify_key == $values[3]) return $data;
  284. }
  285.  
  286. // Delete invalid cookies
  287.  
  288. if($del_invalid) $this->delete_cookie($name);
  289.  
  290. // return false
  291.  
  292. return false;
  293. }
  294.  
  295. /**************************************************************************
  296.   Set a classic cookie ( unsecure )
  297.  
  298.   @access public
  299.   @param string $name cookie name
  300.   @param string $value cookie value
  301.   @param int $expire cookie TTL
  302.   @param string $path cookie path
  303.   @param string $domain cookie domain
  304.   @param bool $secure when TRUE, requires SSL connection
  305.   @param bool $httponly when true, cookie only available over HTTP
  306.   protocol
  307.   @return null function does not return anything
  308.   /**************************************************************************/
  309.  
  310. function set_classic_cookie(
  311. $name,
  312. $value,
  313. $expire = 0,
  314. $path = '/',
  315. $domain = '',
  316. $secure = false,
  317. $httponly = true
  318. ) {
  319. // DEPRECIATED: for use with PHP < 5.2
  320.  
  321. if($httponly === false) setcookie(
  322. $name,
  323. $value,
  324. $expire,
  325. $path,
  326. $domain,
  327. $secure
  328. );
  329. else setcookie(
  330. $name,
  331. $value,
  332. $expire,
  333. $path,
  334. $domain,
  335. $httponly
  336. );
  337. }
  338.  
  339. /**************************************************************************
  340.   Check if cookie exists
  341.  
  342.   @access public
  343.   @param $name name of cookie to check
  344.   @return bool TRUE if exists, FALSE if doesn't
  345.   /**************************************************************************/
  346.  
  347. public function cookie_exists($name)
  348. {
  349. return isset($_COOKIE[$name]);
  350. }
  351.  
  352. /**************************************************************************
  353.   Secure Cookie Value
  354.  
  355.   the initial value is transformed with this protocol :
  356.  
  357.   secure_value = cid|expire|base64((value)k,expire)|HMAC(cid|expire|value,k)
  358.   where k = HMAC(cid|expire, sk) and sk is server's secret key.
  359.  
  360.   (value)k,md5(expire) is the result of a cryptographic function
  361.   ( ie: AES256 ) on "value" with key k and initialization vector
  362.   md5(expire).
  363.  
  364.   @access protected
  365.   @param string $value insecure value
  366.   @param string $cid cookie id
  367.   @param int $expire data TTL
  368.   @return string secured value
  369.   /**************************************************************************/
  370.  
  371. protected function secure_cookie_value($value, $cid, $expire)
  372. {
  373. // generate key
  374.  
  375. $key = hash_hmac(self::$halg, $cid.$expire, self::$key);
  376.  
  377. // encrypt data
  378.  
  379. if($this->get_hi_c()) {
  380. $secure_value = base64_encode($this->encrypt(
  381. $value,
  382. $key,
  383. md5($expire)
  384. ));
  385. }
  386. else
  387. {
  388. $secure_value = base64_encode($value);
  389. }
  390.  
  391. // generate verification key
  392.  
  393. if(self::$ssl && isset($_SERVER['SSL_SESSION_ID']))
  394. {
  395. $verify_key = hash_hmac(
  396. self::$halg,
  397. $cid.$expire.$value.$_SERVER['SSL_SESSION_ID'],
  398. $key
  399. );
  400. }
  401. else
  402. {
  403. $verify_key = hash_hmac(
  404. self::$halg,
  405. $cid.$expire.$value,
  406. $key
  407. );
  408. }
  409.  
  410. // prepare data
  411.  
  412. $result = array($cid,$expire,$secure_value,$verify_key);
  413.  
  414. return implode('|', $result);
  415. }
  416.  
  417. /**************************************************************************
  418.   Encrypt a given data with a given key and a given initialization vector
  419.  
  420.   @access protected
  421.   @param string $data data to encrypt
  422.   @param string $key secret key
  423.   @param string $iv initialisation vector
  424.   @return string encrypted data
  425.   /**************************************************************************/
  426.  
  427. protected function encrypt($data, $key, $iv)
  428. {
  429. // prepare the initialization vectore
  430.  
  431. $iv = $this->validate_iv($iv);
  432.  
  433. // prepare the secret key
  434.  
  435. $key = $this->validate_key($key);
  436.  
  437. // encrypt data
  438.  
  439. mcrypt_generic_init(self::$cmod, $key, $iv);
  440. $res = mcrypt_generic(self::$cmod, $data);
  441. mcrypt_generic_deinit(self::$cmod);
  442.  
  443. // return encrypted data
  444.  
  445. return $res;
  446. }
  447.  
  448. /**************************************************************************
  449.   Decrypt a given data with a given key and a given initialization vector
  450.  
  451.   @access protected
  452.   @param string $data data to decrypt
  453.   @param string $key secret key
  454.   @param string $iv initialization vector
  455.   @return string decrypted data
  456.   /**************************************************************************/
  457.  
  458. protected function decrypt($data, $key, $iv)
  459. {
  460. // prepare the initialization vector
  461.  
  462. $iv = $this->validate_iv($iv);
  463.  
  464. // prepare the secret key
  465.  
  466. $key = $this->validate_key($key);
  467.  
  468. // decrypt data
  469.  
  470. mcrypt_generic_init(self::$cmod, $key, $iv);
  471. $decrypted_data = mdecrypt_generic(self::$cmod, $data);
  472. $res = str_replace("\x0", '', $decrypted_data);
  473. mcrypt_generic_deinit(self::$cmod);
  474.  
  475. // return decrypted data
  476.  
  477. return $res;
  478. }
  479.  
  480. /**************************************************************************
  481.   Validate initialization vector
  482.  
  483.   If the given IV is too long for the selected mcrypt algorithm, it will
  484.   be truncated.
  485.  
  486.   @access protected
  487.   @param string $iv Initialization vector
  488.   @return string truncated initialization vector
  489.   /**************************************************************************/
  490.  
  491. protected function validate_iv($iv)
  492. {
  493. // get IV size
  494.  
  495. $iv_size = mcrypt_enc_get_iv_size(self::$cmod);
  496.  
  497. // truncate IV
  498.  
  499. if(strlen($iv) > $iv_size) $iv = substr($iv, 0, $iv_size);
  500.  
  501. // return truncated IV
  502.  
  503. return $iv;
  504. }
  505.  
  506. /**************************************************************************
  507.   Validate key
  508.  
  509.   If the given secret key is too long for the selected mcrypt algorithm,
  510.   it will be truncated.
  511.  
  512.   @access protected
  513.   @param string $key Secret key
  514.   @return string Truncated secret key
  515.   /**************************************************************************/
  516.  
  517. protected function validate_key($key)
  518. {
  519. // get key size
  520.  
  521. $key_size = mcrypt_enc_get_key_size(self::$cmod);
  522.  
  523. // truncate key
  524.  
  525. if(strlen($key) > $key_size) $key = substr($key, 0, $key_size);
  526.  
  527. // return truncated key
  528.  
  529. return $key;
  530. }
  531. }

Report this snippet  

You need to login to post a comment.