CSecurityManager.php 22 KB

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