You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
12 KiB

7 years ago
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
  33. * @license http://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 1.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Encryption Class
  41. *
  42. * Provides two-way keyed encoding using Mcrypt
  43. *
  44. * @package CodeIgniter
  45. * @subpackage Libraries
  46. * @category Libraries
  47. * @author EllisLab Dev Team
  48. * @link https://codeigniter.com/user_guide/libraries/encryption.html
  49. */
  50. class CI_Encrypt {
  51. /**
  52. * Reference to the user's encryption key
  53. *
  54. * @var string
  55. */
  56. public $encryption_key = '';
  57. /**
  58. * Type of hash operation
  59. *
  60. * @var string
  61. */
  62. protected $_hash_type = 'sha1';
  63. /**
  64. * Flag for the existence of mcrypt
  65. *
  66. * @var bool
  67. */
  68. protected $_mcrypt_exists = FALSE;
  69. /**
  70. * Current cipher to be used with mcrypt
  71. *
  72. * @var string
  73. */
  74. protected $_mcrypt_cipher;
  75. /**
  76. * Method for encrypting/decrypting data
  77. *
  78. * @var int
  79. */
  80. protected $_mcrypt_mode;
  81. /**
  82. * Initialize Encryption class
  83. *
  84. * @return void
  85. */
  86. public function __construct()
  87. {
  88. if (($this->_mcrypt_exists = function_exists('mcrypt_encrypt')) === FALSE)
  89. {
  90. show_error('The Encrypt library requires the Mcrypt extension.');
  91. }
  92. log_message('info', 'Encrypt Class Initialized');
  93. }
  94. // --------------------------------------------------------------------
  95. /**
  96. * Fetch the encryption key
  97. *
  98. * Returns it as MD5 in order to have an exact-length 128 bit key.
  99. * Mcrypt is sensitive to keys that are not the correct length
  100. *
  101. * @param string
  102. * @return string
  103. */
  104. public function get_key($key = '')
  105. {
  106. if ($key === '')
  107. {
  108. if ($this->encryption_key !== '')
  109. {
  110. return $this->encryption_key;
  111. }
  112. $key = config_item('encryption_key');
  113. if ( ! self::strlen($key))
  114. {
  115. show_error('In order to use the encryption class requires that you set an encryption key in your config file.');
  116. }
  117. }
  118. return md5($key);
  119. }
  120. // --------------------------------------------------------------------
  121. /**
  122. * Set the encryption key
  123. *
  124. * @param string
  125. * @return CI_Encrypt
  126. */
  127. public function set_key($key = '')
  128. {
  129. $this->encryption_key = $key;
  130. return $this;
  131. }
  132. // --------------------------------------------------------------------
  133. /**
  134. * Encode
  135. *
  136. * Encodes the message string using bitwise XOR encoding.
  137. * The key is combined with a random hash, and then it
  138. * too gets converted using XOR. The whole thing is then run
  139. * through mcrypt using the randomized key. The end result
  140. * is a double-encrypted message string that is randomized
  141. * with each call to this function, even if the supplied
  142. * message and key are the same.
  143. *
  144. * @param string the string to encode
  145. * @param string the key
  146. * @return string
  147. */
  148. public function encode($string, $key = '')
  149. {
  150. return base64_encode($this->mcrypt_encode($string, $this->get_key($key)));
  151. }
  152. // --------------------------------------------------------------------
  153. /**
  154. * Decode
  155. *
  156. * Reverses the above process
  157. *
  158. * @param string
  159. * @param string
  160. * @return string
  161. */
  162. public function decode($string, $key = '')
  163. {
  164. if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string) OR base64_encode(base64_decode($string)) !== $string)
  165. {
  166. return FALSE;
  167. }
  168. return $this->mcrypt_decode(base64_decode($string), $this->get_key($key));
  169. }
  170. // --------------------------------------------------------------------
  171. /**
  172. * Encode from Legacy
  173. *
  174. * Takes an encoded string from the original Encryption class algorithms and
  175. * returns a newly encoded string using the improved method added in 2.0.0
  176. * This allows for backwards compatibility and a method to transition to the
  177. * new encryption algorithms.
  178. *
  179. * For more details, see https://codeigniter.com/user_guide/installation/upgrade_200.html#encryption
  180. *
  181. * @param string
  182. * @param int (mcrypt mode constant)
  183. * @param string
  184. * @return string
  185. */
  186. public function encode_from_legacy($string, $legacy_mode = MCRYPT_MODE_ECB, $key = '')
  187. {
  188. if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string))
  189. {
  190. return FALSE;
  191. }
  192. // decode it first
  193. // set mode temporarily to what it was when string was encoded with the legacy
  194. // algorithm - typically MCRYPT_MODE_ECB
  195. $current_mode = $this->_get_mode();
  196. $this->set_mode($legacy_mode);
  197. $key = $this->get_key($key);
  198. $dec = base64_decode($string);
  199. if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE)
  200. {
  201. $this->set_mode($current_mode);
  202. return FALSE;
  203. }
  204. $dec = $this->_xor_decode($dec, $key);
  205. // set the mcrypt mode back to what it should be, typically MCRYPT_MODE_CBC
  206. $this->set_mode($current_mode);
  207. // and re-encode
  208. return base64_encode($this->mcrypt_encode($dec, $key));
  209. }
  210. // --------------------------------------------------------------------
  211. /**
  212. * XOR Decode
  213. *
  214. * Takes an encoded string and key as input and generates the
  215. * plain-text original message
  216. *
  217. * @param string
  218. * @param string
  219. * @return string
  220. */
  221. protected function _xor_decode($string, $key)
  222. {
  223. $string = $this->_xor_merge($string, $key);
  224. $dec = '';
  225. for ($i = 0, $l = self::strlen($string); $i < $l; $i++)
  226. {
  227. $dec .= ($string[$i++] ^ $string[$i]);
  228. }
  229. return $dec;
  230. }
  231. // --------------------------------------------------------------------
  232. /**
  233. * XOR key + string Combiner
  234. *
  235. * Takes a string and key as input and computes the difference using XOR
  236. *
  237. * @param string
  238. * @param string
  239. * @return string
  240. */
  241. protected function _xor_merge($string, $key)
  242. {
  243. $hash = $this->hash($key);
  244. $str = '';
  245. for ($i = 0, $ls = self::strlen($string), $lh = self::strlen($hash); $i < $ls; $i++)
  246. {
  247. $str .= $string[$i] ^ $hash[($i % $lh)];
  248. }
  249. return $str;
  250. }
  251. // --------------------------------------------------------------------
  252. /**
  253. * Encrypt using Mcrypt
  254. *
  255. * @param string
  256. * @param string
  257. * @return string
  258. */
  259. public function mcrypt_encode($data, $key)
  260. {
  261. $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());
  262. $init_vect = mcrypt_create_iv($init_size, MCRYPT_DEV_URANDOM);
  263. return $this->_add_cipher_noise($init_vect.mcrypt_encrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), $key);
  264. }
  265. // --------------------------------------------------------------------
  266. /**
  267. * Decrypt using Mcrypt
  268. *
  269. * @param string
  270. * @param string
  271. * @return string
  272. */
  273. public function mcrypt_decode($data, $key)
  274. {
  275. $data = $this->_remove_cipher_noise($data, $key);
  276. $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());
  277. if ($init_size > self::strlen($data))
  278. {
  279. return FALSE;
  280. }
  281. $init_vect = self::substr($data, 0, $init_size);
  282. $data = self::substr($data, $init_size);
  283. return rtrim(mcrypt_decrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), "\0");
  284. }
  285. // --------------------------------------------------------------------
  286. /**
  287. * Adds permuted noise to the IV + encrypted data to protect
  288. * against Man-in-the-middle attacks on CBC mode ciphers
  289. * http://www.ciphersbyritter.com/GLOSSARY.HTM#IV
  290. *
  291. * @param string
  292. * @param string
  293. * @return string
  294. */
  295. protected function _add_cipher_noise($data, $key)
  296. {
  297. $key = $this->hash($key);
  298. $str = '';
  299. for ($i = 0, $j = 0, $ld = self::strlen($data), $lk = self::strlen($key); $i < $ld; ++$i, ++$j)
  300. {
  301. if ($j >= $lk)
  302. {
  303. $j = 0;
  304. }
  305. $str .= chr((ord($data[$i]) + ord($key[$j])) % 256);
  306. }
  307. return $str;
  308. }
  309. // --------------------------------------------------------------------
  310. /**
  311. * Removes permuted noise from the IV + encrypted data, reversing
  312. * _add_cipher_noise()
  313. *
  314. * Function description
  315. *
  316. * @param string $data
  317. * @param string $key
  318. * @return string
  319. */
  320. protected function _remove_cipher_noise($data, $key)
  321. {
  322. $key = $this->hash($key);
  323. $str = '';
  324. for ($i = 0, $j = 0, $ld = self::strlen($data), $lk = self::strlen($key); $i < $ld; ++$i, ++$j)
  325. {
  326. if ($j >= $lk)
  327. {
  328. $j = 0;
  329. }
  330. $temp = ord($data[$i]) - ord($key[$j]);
  331. if ($temp < 0)
  332. {
  333. $temp += 256;
  334. }
  335. $str .= chr($temp);
  336. }
  337. return $str;
  338. }
  339. // --------------------------------------------------------------------
  340. /**
  341. * Set the Mcrypt Cipher
  342. *
  343. * @param int
  344. * @return CI_Encrypt
  345. */
  346. public function set_cipher($cipher)
  347. {
  348. $this->_mcrypt_cipher = $cipher;
  349. return $this;
  350. }
  351. // --------------------------------------------------------------------
  352. /**
  353. * Set the Mcrypt Mode
  354. *
  355. * @param int
  356. * @return CI_Encrypt
  357. */
  358. public function set_mode($mode)
  359. {
  360. $this->_mcrypt_mode = $mode;
  361. return $this;
  362. }
  363. // --------------------------------------------------------------------
  364. /**
  365. * Get Mcrypt cipher Value
  366. *
  367. * @return int
  368. */
  369. protected function _get_cipher()
  370. {
  371. if ($this->_mcrypt_cipher === NULL)
  372. {
  373. return $this->_mcrypt_cipher = MCRYPT_RIJNDAEL_256;
  374. }
  375. return $this->_mcrypt_cipher;
  376. }
  377. // --------------------------------------------------------------------
  378. /**
  379. * Get Mcrypt Mode Value
  380. *
  381. * @return int
  382. */
  383. protected function _get_mode()
  384. {
  385. if ($this->_mcrypt_mode === NULL)
  386. {
  387. return $this->_mcrypt_mode = MCRYPT_MODE_CBC;
  388. }
  389. return $this->_mcrypt_mode;
  390. }
  391. // --------------------------------------------------------------------
  392. /**
  393. * Set the Hash type
  394. *
  395. * @param string
  396. * @return void
  397. */
  398. public function set_hash($type = 'sha1')
  399. {
  400. $this->_hash_type = in_array($type, hash_algos()) ? $type : 'sha1';
  401. }
  402. // --------------------------------------------------------------------
  403. /**
  404. * Hash encode a string
  405. *
  406. * @param string
  407. * @return string
  408. */
  409. public function hash($str)
  410. {
  411. return hash($this->_hash_type, $str);
  412. }
  413. // --------------------------------------------------------------------
  414. /**
  415. * Byte-safe strlen()
  416. *
  417. * @param string $str
  418. * @return int
  419. */
  420. protected static function strlen($str)
  421. {
  422. return defined('MB_OVERLOAD_STRING')
  423. ? mb_strlen($str, '8bit')
  424. : strlen($str);
  425. }
  426. // --------------------------------------------------------------------
  427. /**
  428. * Byte-safe substr()
  429. *
  430. * @param string $str
  431. * @param int $start
  432. * @param int $length
  433. * @return string
  434. */
  435. protected static function substr($str, $start, $length = NULL)
  436. {
  437. if (defined('MB_OVERLOAD_STRING'))
  438. {
  439. // mb_substr($str, $start, null, '8bit') returns an empty
  440. // string on PHP 5.3
  441. isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
  442. return mb_substr($str, $start, $length, '8bit');
  443. }
  444. return isset($length)
  445. ? substr($str, $start, $length)
  446. : substr($str, $start);
  447. }
  448. }