PageRenderTime 58ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/base/CSecurityManager.php

http://github.com/yiisoft/yii
PHP | 648 lines | 336 code | 54 blank | 258 comment | 53 complexity | d5bd3ec012de5477904198120eeaa125 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file contains classes implementing security manager feature.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CSecurityManager provides private keys, hashing and encryption functions.
  12. *
  13. * CSecurityManager is used by Yii components and applications for security-related purpose.
  14. * For example, it is used in cookie validation feature to prevent cookie data
  15. * from being tampered.
  16. *
  17. * CSecurityManager is mainly used to protect data from being tampered and viewed.
  18. * It can generate HMAC and encrypt the data. The private key used to generate HMAC
  19. * is set by {@link setValidationKey ValidationKey}. The key used to encrypt data is
  20. * specified by {@link setEncryptionKey EncryptionKey}. If the above keys are not
  21. * explicitly set, random keys will be generated and used.
  22. *
  23. * To protected data with HMAC, call {@link hashData()}; and to check if the data
  24. * is tampered, call {@link validateData()}, which will return the real data if
  25. * it is not tampered. The algorithm used to generated HMAC is specified by
  26. * {@link validation}.
  27. *
  28. * To encrypt and decrypt data, call {@link encrypt()} and {@link decrypt()}
  29. * respectively, which uses 3DES encryption algorithm. Note, the PHP Mcrypt
  30. * extension must be installed and loaded.
  31. *
  32. * CSecurityManager is a core application component that can be accessed via
  33. * {@link CApplication::getSecurityManager()}.
  34. *
  35. * @property string $validationKey The private key used to generate HMAC.
  36. * If the key is not explicitly set, a random one is generated and returned.
  37. * @property string $encryptionKey The private key used to encrypt/decrypt data.
  38. * If the key is not explicitly set, a random one is generated and returned.
  39. * @property string $validation
  40. *
  41. * @author Qiang Xue <qiang.xue@gmail.com>
  42. * @package system.base
  43. * @since 1.0
  44. */
  45. class CSecurityManager extends CApplicationComponent
  46. {
  47. const STATE_VALIDATION_KEY='Yii.CSecurityManager.validationkey';
  48. const STATE_ENCRYPTION_KEY='Yii.CSecurityManager.encryptionkey';
  49. /**
  50. * @var array known minimum lengths per encryption algorithm
  51. */
  52. protected static $encryptionKeyMinimumLengths=array(
  53. 'blowfish'=>4,
  54. 'arcfour'=>5,
  55. 'rc2'=>5,
  56. );
  57. /**
  58. * @var boolean if encryption key should be validated
  59. * @deprecated
  60. */
  61. public $validateEncryptionKey=true;
  62. /**
  63. * @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
  64. * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
  65. * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
  66. *
  67. * Defaults to 'sha1', meaning using SHA1 hash algorithm.
  68. * @since 1.1.3
  69. */
  70. public $hashAlgorithm='sha1';
  71. /**
  72. * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}.
  73. * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
  74. *
  75. * This property can also be configured as an array. In this case, the array elements will be passed in order
  76. * as parameters to mcrypt_module_open. For example, <code>array('rijndael-128', '', 'ofb', '')</code>.
  77. *
  78. * Defaults to AES
  79. *
  80. * Note: MCRYPT_RIJNDAEL_192 and MCRYPT_RIJNDAEL_256 are *not* AES-192 and AES-256. The numbers of the MCRYPT_RIJNDAEL
  81. * constants refer to the block size, whereas the numbers of the AES variants refer to the key length. AES is Rijndael
  82. * with a block size of 128 bits and a key length of 128 bits, 192 bits or 256 bits. So to use AES in Mcrypt, you need
  83. * MCRYPT_RIJNDAEL_128 and a key with 16 bytes (AES-128), 24 bytes (AES-192) or 32 bytes (AES-256). The other two
  84. * Rijndael variants in Mcrypt should be avoided, because they're not standardized and have been analyzed much less
  85. * than AES.
  86. *
  87. * @since 1.1.3
  88. */
  89. public $cryptAlgorithm='rijndael-128';
  90. private $_validationKey;
  91. private $_encryptionKey;
  92. private $_mbstring;
  93. public function init()
  94. {
  95. parent::init();
  96. $this->_mbstring=extension_loaded('mbstring');
  97. }
  98. /**
  99. * @return string a randomly generated private key.
  100. * @deprecated in favor of {@link generateRandomString()} since 1.1.14. Never use this method.
  101. */
  102. protected function generateRandomKey()
  103. {
  104. return $this->generateRandomString(32);
  105. }
  106. /**
  107. * @return string the private key used to generate HMAC.
  108. * If the key is not explicitly set, a random one is generated and returned.
  109. * @throws CException in case random string cannot be generated.
  110. */
  111. public function getValidationKey()
  112. {
  113. if($this->_validationKey!==null)
  114. return $this->_validationKey;
  115. else
  116. {
  117. if(($key=Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY))!==null)
  118. $this->setValidationKey($key);
  119. else
  120. {
  121. if(($key=$this->generateRandomString(32,true))===false)
  122. if(($key=$this->generateRandomString(32,false))===false)
  123. throw new CException(Yii::t('yii',
  124. 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.'));
  125. $this->setValidationKey($key);
  126. Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY,$key);
  127. }
  128. return $this->_validationKey;
  129. }
  130. }
  131. /**
  132. * @param string $value the key used to generate HMAC
  133. * @throws CException if the key is empty
  134. */
  135. public function setValidationKey($value)
  136. {
  137. if(!empty($value))
  138. $this->_validationKey=$value;
  139. else
  140. throw new CException(Yii::t('yii','CSecurityManager.validationKey cannot be empty.'));
  141. }
  142. /**
  143. * @return string the private key used to encrypt/decrypt data.
  144. * If the key is not explicitly set, a random one is generated and returned.
  145. * @throws CException in case random string cannot be generated.
  146. */
  147. public function getEncryptionKey()
  148. {
  149. if($this->_encryptionKey!==null)
  150. return $this->_encryptionKey;
  151. else
  152. {
  153. if(($key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY))!==null)
  154. $this->setEncryptionKey($key);
  155. else
  156. {
  157. if(($key=$this->generateRandomString(32,true))===false)
  158. if(($key=$this->generateRandomString(32,false))===false)
  159. throw new CException(Yii::t('yii',
  160. 'CSecurityManager::generateRandomString() cannot generate random string in the current environment.'));
  161. $this->setEncryptionKey($key);
  162. Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY,$key);
  163. }
  164. return $this->_encryptionKey;
  165. }
  166. }
  167. /**
  168. * @param string $value the key used to encrypt/decrypt data.
  169. * @throws CException if the key is empty
  170. */
  171. public function setEncryptionKey($value)
  172. {
  173. $this->validateEncryptionKey($value);
  174. $this->_encryptionKey=$value;
  175. }
  176. /**
  177. * This method has been deprecated since version 1.1.3.
  178. * Please use {@link hashAlgorithm} instead.
  179. * @return string -
  180. * @deprecated
  181. */
  182. public function getValidation()
  183. {
  184. return $this->hashAlgorithm;
  185. }
  186. /**
  187. * This method has been deprecated since version 1.1.3.
  188. * Please use {@link hashAlgorithm} instead.
  189. * @param string $value -
  190. * @deprecated
  191. */
  192. public function setValidation($value)
  193. {
  194. $this->hashAlgorithm=$value;
  195. }
  196. /**
  197. * Encrypts data.
  198. * @param string $data data to be encrypted.
  199. * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
  200. * @return string the encrypted data
  201. * @throws CException if PHP Mcrypt extension is not loaded or key is invalid
  202. */
  203. public function encrypt($data,$key=null)
  204. {
  205. if($key===null)
  206. $key=$this->getEncryptionKey();
  207. $this->validateEncryptionKey($key);
  208. $module=$this->openCryptModule();
  209. srand();
  210. $iv=@mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
  211. @mcrypt_generic_init($module,$key,$iv);
  212. $encrypted=$iv.@mcrypt_generic($module,$data);
  213. @mcrypt_generic_deinit($module);
  214. @mcrypt_module_close($module);
  215. return $encrypted;
  216. }
  217. /**
  218. * Decrypts data
  219. * @param string $data data to be decrypted.
  220. * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
  221. * @return string the decrypted data
  222. * @throws CException if PHP Mcrypt extension is not loaded or key is invalid
  223. */
  224. public function decrypt($data,$key=null)
  225. {
  226. if($key===null)
  227. $key=$this->getEncryptionKey();
  228. $this->validateEncryptionKey($key);
  229. $module=$this->openCryptModule();
  230. $ivSize=@mcrypt_enc_get_iv_size($module);
  231. $iv=$this->substr($data,0,$ivSize);
  232. @mcrypt_generic_init($module,$key,$iv);
  233. $decrypted=@mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data)));
  234. @mcrypt_generic_deinit($module);
  235. @mcrypt_module_close($module);
  236. return rtrim($decrypted,"\0");
  237. }
  238. /**
  239. * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
  240. * @throws CException if failed to initialize the mcrypt module or PHP mcrypt extension
  241. * @return resource the mycrypt module handle.
  242. * @since 1.1.3
  243. */
  244. protected function openCryptModule()
  245. {
  246. if(extension_loaded('mcrypt'))
  247. {
  248. if(is_array($this->cryptAlgorithm))
  249. $module=@call_user_func_array('mcrypt_module_open',$this->cryptAlgorithm);
  250. else
  251. $module=@mcrypt_module_open($this->cryptAlgorithm,'', MCRYPT_MODE_CBC,'');
  252. if($module===false)
  253. throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.'));
  254. return $module;
  255. }
  256. else
  257. throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
  258. }
  259. /**
  260. * Prefixes data with an HMAC.
  261. * @param string $data data to be hashed.
  262. * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
  263. * @return string data prefixed with HMAC
  264. */
  265. public function hashData($data,$key=null)
  266. {
  267. return $this->computeHMAC($data,$key).$data;
  268. }
  269. /**
  270. * Validates if data is tampered.
  271. * @param string $data data to be validated. The data must be previously
  272. * generated using {@link hashData()}.
  273. * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
  274. * @return string the real data with HMAC stripped off. False if the data
  275. * is tampered.
  276. */
  277. public function validateData($data,$key=null)
  278. {
  279. if (!is_string($data))
  280. return false;
  281. $len=$this->strlen($this->computeHMAC('test'));
  282. if($this->strlen($data)>=$len)
  283. {
  284. $hmac=$this->substr($data,0,$len);
  285. $data2=$this->substr($data,$len,$this->strlen($data));
  286. return $this->compareString($hmac,$this->computeHMAC($data2,$key))?$data2:false;
  287. }
  288. else
  289. return false;
  290. }
  291. /**
  292. * Computes the HMAC for the data with {@link getValidationKey validationKey}. This method has been made public
  293. * since 1.1.14.
  294. * @param string $data data to be generated HMAC.
  295. * @param string|null $key the private key to be used for generating HMAC. Defaults to null, meaning using
  296. * {@link validationKey} value.
  297. * @param string|null $hashAlgorithm the name of the hashing algorithm to be used.
  298. * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
  299. * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
  300. * Defaults to null, meaning using {@link hashAlgorithm} value.
  301. * @return string the HMAC for the data.
  302. * @throws CException on unsupported hash algorithm given.
  303. */
  304. public function computeHMAC($data,$key=null,$hashAlgorithm=null)
  305. {
  306. if($key===null)
  307. $key=$this->getValidationKey();
  308. if($hashAlgorithm===null)
  309. $hashAlgorithm=$this->hashAlgorithm;
  310. if(function_exists('hash_hmac'))
  311. return hash_hmac($hashAlgorithm,$data,$key);
  312. if(0===strcasecmp($hashAlgorithm,'sha1'))
  313. {
  314. $pack='H40';
  315. $func='sha1';
  316. }
  317. elseif(0===strcasecmp($hashAlgorithm,'md5'))
  318. {
  319. $pack='H32';
  320. $func='md5';
  321. }
  322. else
  323. {
  324. throw new CException(Yii::t('yii','Only SHA1 and MD5 hashing algorithms are supported when using PHP 5.1.1 or below.'));
  325. }
  326. if($this->strlen($key)>64)
  327. $key=pack($pack,$func($key));
  328. if($this->strlen($key)<64)
  329. $key=str_pad($key,64,chr(0));
  330. $key=$this->substr($key,0,64);
  331. return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data)));
  332. }
  333. /**
  334. * Generate a random ASCII string. Generates only [0-9a-zA-z_~] characters which are all
  335. * transparent in raw URL encoding.
  336. * @param integer $length length of the generated string in characters.
  337. * @param boolean $cryptographicallyStrong set this to require cryptographically strong randomness.
  338. * @return string|boolean random string or false in case it cannot be generated.
  339. * @since 1.1.14
  340. */
  341. public function generateRandomString($length,$cryptographicallyStrong=true)
  342. {
  343. if(($randomBytes=$this->generateRandomBytes($length+2,$cryptographicallyStrong))!==false)
  344. return strtr($this->substr(base64_encode($randomBytes),0,$length),array('+'=>'_','/'=>'~'));
  345. return false;
  346. }
  347. /**
  348. * Generates a string of random bytes.
  349. * @param integer $length number of random bytes to be generated.
  350. * @param boolean $cryptographicallyStrong whether to fail if a cryptographically strong
  351. * result cannot be generated. The method attempts to read from a cryptographically strong
  352. * pseudorandom number generator (CS-PRNG), see
  353. * {@link https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Requirements Wikipedia}.
  354. * However, in some runtime environments, PHP has no access to a CS-PRNG, in which case
  355. * the method returns false if $cryptographicallyStrong is true. When $cryptographicallyStrong is false,
  356. * the method always returns a pseudorandom result but may fall back to using {@link generatePseudoRandomBlock}.
  357. * This method does not guarantee that entropy, from sources external to the CS-PRNG, was mixed into
  358. * the CS-PRNG state between each successive call. The caller can therefore expect non-blocking
  359. * behavior, unlike, for example, reading from /dev/random on Linux, see
  360. * {@link http://eprint.iacr.org/2006/086.pdf Gutterman et al 2006}.
  361. * @return boolean|string generated random binary string or false on failure.
  362. * @since 1.1.14
  363. */
  364. public function generateRandomBytes($length,$cryptographicallyStrong=true)
  365. {
  366. $bytes='';
  367. if(function_exists('openssl_random_pseudo_bytes'))
  368. {
  369. $bytes=openssl_random_pseudo_bytes($length,$strong);
  370. if($this->strlen($bytes)>=$length && ($strong || !$cryptographicallyStrong))
  371. return $this->substr($bytes,0,$length);
  372. }
  373. if(function_exists('mcrypt_create_iv') &&
  374. ($bytes=@mcrypt_create_iv($length, MCRYPT_DEV_URANDOM))!==false &&
  375. $this->strlen($bytes)>=$length)
  376. {
  377. return $this->substr($bytes,0,$length);
  378. }
  379. if(($file=@fopen('/dev/urandom','rb'))!==false &&
  380. ($bytes=@fread($file,$length))!==false &&
  381. (fclose($file) || true) &&
  382. $this->strlen($bytes)>=$length)
  383. {
  384. return $this->substr($bytes,0,$length);
  385. }
  386. $i=0;
  387. while($this->strlen($bytes)<$length &&
  388. ($byte=$this->generateSessionRandomBlock())!==false &&
  389. ++$i<3)
  390. {
  391. $bytes.=$byte;
  392. }
  393. if($this->strlen($bytes)>=$length)
  394. return $this->substr($bytes,0,$length);
  395. if ($cryptographicallyStrong)
  396. return false;
  397. while($this->strlen($bytes)<$length)
  398. $bytes.=$this->generatePseudoRandomBlock();
  399. return $this->substr($bytes,0,$length);
  400. }
  401. /**
  402. * Generate a pseudo random block of data using several sources. On some systems this may be a bit
  403. * better than PHP's {@link mt_rand} built-in function, which is not really random.
  404. * @return string of 64 pseudo random bytes.
  405. * @since 1.1.14
  406. */
  407. public function generatePseudoRandomBlock()
  408. {
  409. $bytes='';
  410. if (function_exists('openssl_random_pseudo_bytes')
  411. && ($bytes=openssl_random_pseudo_bytes(512))!==false
  412. && $this->strlen($bytes)>=512)
  413. {
  414. return $this->substr($bytes,0,512);
  415. }
  416. for($i=0;$i<32;++$i)
  417. $bytes.=pack('S',mt_rand(0,0xffff));
  418. // On UNIX and UNIX-like operating systems the numerical values in `ps`, `uptime` and `iostat`
  419. // ought to be fairly unpredictable. Gather the non-zero digits from those.
  420. foreach(array('ps','uptime','iostat') as $command) {
  421. @exec($command,$commandResult,$retVal);
  422. if(is_array($commandResult) && !empty($commandResult) && $retVal==0)
  423. $bytes.=preg_replace('/[^1-9]/','',implode('',$commandResult));
  424. }
  425. // Gather the current time's microsecond part. Note: this is only a source of entropy on
  426. // the first call! If multiple calls are made, the entropy is only as much as the
  427. // randomness in the time between calls.
  428. $bytes.=$this->substr(microtime(),2,6);
  429. // Concatenate everything gathered, mix it with sha512. hash() is part of PHP core and
  430. // enabled by default but it can be disabled at compile time but we ignore that possibility here.
  431. return hash('sha512',$bytes,true);
  432. }
  433. /**
  434. * Get random bytes from the system entropy source via PHP session manager.
  435. * @return boolean|string 20-byte random binary string or false on error.
  436. * @since 1.1.14
  437. */
  438. public function generateSessionRandomBlock()
  439. {
  440. ini_set('session.entropy_length',20);
  441. if(ini_get('session.entropy_length')!=20)
  442. return false;
  443. // These calls are (supposed to be, according to PHP manual) safe even if
  444. // there is already an active session for the calling script.
  445. @session_start();
  446. @session_regenerate_id();
  447. $bytes=session_id();
  448. if(!$bytes)
  449. return false;
  450. // $bytes has 20 bytes of entropy but the session manager converts the binary
  451. // random bytes into something readable. We have to convert that back.
  452. // SHA-1 should do it without losing entropy.
  453. return sha1($bytes,true);
  454. }
  455. /**
  456. * Returns the length of the given string.
  457. * If available uses the multibyte string function mb_strlen.
  458. * @param string $string the string being measured for length
  459. * @return integer the length of the string
  460. */
  461. private function strlen($string)
  462. {
  463. return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string);
  464. }
  465. /**
  466. * Returns the portion of string specified by the start and length parameters.
  467. * If available uses the multibyte string function mb_substr
  468. * @param string $string the input string. Must be one character or longer.
  469. * @param integer $start the starting position
  470. * @param integer $length the desired portion length
  471. * @return string the extracted part of string, or FALSE on failure or an empty string.
  472. */
  473. private function substr($string,$start,$length)
  474. {
  475. return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length);
  476. }
  477. /**
  478. * Checks if a key is valid for {@link cryptAlgorithm}.
  479. * @param string $key the key to check
  480. * @return boolean the validation result
  481. * @throws CException if the supported key lengths of the cipher are unknown
  482. */
  483. protected function validateEncryptionKey($key)
  484. {
  485. if(is_string($key))
  486. {
  487. $cryptAlgorithm = is_array($this->cryptAlgorithm) ? $this->cryptAlgorithm[0] : $this->cryptAlgorithm;
  488. $supportedKeyLengths=@mcrypt_module_get_supported_key_sizes($cryptAlgorithm);
  489. if($supportedKeyLengths)
  490. {
  491. if(!in_array($this->strlen($key),$supportedKeyLengths)) {
  492. throw new CException(Yii::t('yii','Encryption key length can be {keyLengths}.',array('{keyLengths}'=>implode(',',$supportedKeyLengths))));
  493. }
  494. }
  495. elseif(isset(self::$encryptionKeyMinimumLengths[$cryptAlgorithm]))
  496. {
  497. $minLength=self::$encryptionKeyMinimumLengths[$cryptAlgorithm];
  498. $maxLength=@mcrypt_module_get_algo_key_size($cryptAlgorithm);
  499. if($this->strlen($key)<$minLength || $this->strlen($key)>$maxLength)
  500. throw new CException(Yii::t('yii','Encryption key length must be between {minLength} and {maxLength}.',array('{minLength}'=>$minLength,'{maxLength}'=>$maxLength)));
  501. }
  502. else
  503. throw new CException(Yii::t('yii','Failed to validate key. Supported key lengths of cipher not known.'));
  504. }
  505. else
  506. throw new CException(Yii::t('yii','Encryption key should be a string.'));
  507. }
  508. /**
  509. * Decrypts legacy ciphertext which was produced by the old, broken implementation of encrypt().
  510. * @deprecated use only to convert data encrypted prior to 1.1.16
  511. * @param string $data data to be decrypted.
  512. * @param string $key the decryption key. This defaults to null, meaning the key should be loaded from persistent storage.
  513. * @param string|array $cipher the algorithm to be used
  514. * @return string the decrypted data
  515. * @throws CException if PHP Mcrypt extension is not loaded
  516. * @throws CException if the key is missing
  517. */
  518. public function legacyDecrypt($data,$key=null,$cipher='des')
  519. {
  520. if (!$key)
  521. {
  522. $key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY);
  523. if(!$key)
  524. throw new CException(Yii::t('yii','No encryption key specified.'));
  525. $key = md5($key);
  526. }
  527. if(extension_loaded('mcrypt'))
  528. {
  529. if(is_array($cipher))
  530. $module=@call_user_func_array('mcrypt_module_open',$cipher);
  531. else
  532. $module=@mcrypt_module_open($cipher,'', MCRYPT_MODE_CBC,'');
  533. if($module===false)
  534. throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.'));
  535. }
  536. else
  537. throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
  538. $derivedKey=$this->substr($key,0,@mcrypt_enc_get_key_size($module));
  539. $ivSize=@mcrypt_enc_get_iv_size($module);
  540. $iv=$this->substr($data,0,$ivSize);
  541. @mcrypt_generic_init($module,$derivedKey,$iv);
  542. $decrypted=@mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data)));
  543. @mcrypt_generic_deinit($module);
  544. @mcrypt_module_close($module);
  545. return rtrim($decrypted,"\0");
  546. }
  547. /**
  548. * Performs string comparison using timing attack resistant approach.
  549. * @see http://codereview.stackexchange.com/questions/13512
  550. * @param string $expected string to compare.
  551. * @param string $actual user-supplied string.
  552. * @return boolean whether strings are equal.
  553. */
  554. public function compareString($expected,$actual)
  555. {
  556. $expected.="\0";
  557. $actual.="\0";
  558. $expectedLength=$this->strlen($expected);
  559. $actualLength=$this->strlen($actual);
  560. $diff=$expectedLength-$actualLength;
  561. for($i=0;$i<$actualLength;$i++)
  562. $diff|=(ord($actual[$i])^ord($expected[$i%$expectedLength]));
  563. return $diff===0;
  564. }
  565. /**
  566. * Masks a token to make it uncompressible.
  567. * Applies a random mask to the token and prepends the mask used to the result making the string always unique.
  568. * Used to mitigate BREACH attack by randomizing how token is outputted on each request.
  569. * @param string $token An unmasked token.
  570. * @return string A masked token.
  571. * @since 1.1.18
  572. */
  573. public function maskToken($token)
  574. {
  575. // The number of bytes in a mask is always equal to the number of bytes in a token.
  576. $mask=$this->generateRandomString($this->strlen($token));
  577. return strtr(base64_encode($mask.($mask^$token)),'+/','-_');
  578. }
  579. /**
  580. * Unmasks a token previously masked by `maskToken`.
  581. * @param string $maskedToken A masked token.
  582. * @return string An unmasked token, or an empty string in case of token format is invalid.
  583. * @since 1.1.18
  584. */
  585. public function unmaskToken($maskedToken)
  586. {
  587. $decoded=base64_decode(strtr($maskedToken,'-_','+/'));
  588. $length=$this->strlen($decoded)/2;
  589. // Check if the masked token has an even length.
  590. if(!is_int($length))
  591. return '';
  592. return $this->substr($decoded,$length,$length)^$this->substr($decoded,0,$length);
  593. }
  594. }