blob: d47d65e8a603d433cf3d55f51fd022c20c965675 [file] [log] [blame]
Andrey Andreev818aedb2014-02-03 11:30:25 +02001<?php
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.2.4 or newer
6 *
7 * NOTICE OF LICENSE
8 *
9 * Licensed under the Open Software License version 3.0
10 *
11 * This source file is subject to the Open Software License (OSL 3.0) that is
12 * bundled with this package in the files license.txt / license.rst. It is
13 * also available through the world wide web at this URL:
14 * http://opensource.org/licenses/OSL-3.0
15 * If you did not receive a copy of the license and are unable to obtain it
16 * through the world wide web, please send an email to
17 * licensing@ellislab.com so we can send you a copy immediately.
18 *
19 * @package CodeIgniter
20 * @author EllisLab Dev Team
darwinel871754a2014-02-11 17:34:57 +010021 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
Andrey Andreev818aedb2014-02-03 11:30:25 +020022 * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
23 * @link http://codeigniter.com
24 * @since Version 3.0
25 * @filesource
26 */
27defined('BASEPATH') OR exit('No direct script access allowed');
28
29/**
30 * CodeIgniter Encryption Class
31 *
32 * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions.
33 *
34 * @package CodeIgniter
35 * @subpackage Libraries
36 * @category Libraries
37 * @author Andrey Andreev
38 * @link http://codeigniter.com/user_guide/libraries/encryption.html
39 */
40class CI_Encryption {
41
42 /**
43 * Encryption cipher
44 *
45 * @var string
46 */
Andrey Andreev81e10642014-02-07 01:43:36 +020047 protected $_cipher = 'aes-128';
Andrey Andreev818aedb2014-02-03 11:30:25 +020048
49 /**
50 * Cipher mode
51 *
52 * @var string
53 */
54 protected $_mode = 'cbc';
55
56 /**
57 * Cipher handle
58 *
59 * @var mixed
60 */
61 protected $_handle;
62
63 /**
64 * Encryption key
65 *
66 * @var string
67 */
68 protected $_key;
69
70 /**
71 * PHP extension to be used
72 *
73 * @var string
74 */
75 protected $_driver;
76
77 /**
78 * List of usable drivers (PHP extensions)
79 *
80 * @var array
81 */
82 protected $_drivers = array();
83
Andrey Andreev29cade42014-02-04 14:05:58 +020084 /**
Andrey Andreevf4017672014-02-05 18:51:15 +020085 * List of available modes
86 *
87 * @var array
88 */
89 protected $_modes = array(
90 'mcrypt' => array(
91 'cbc' => 'cbc',
92 'ecb' => 'ecb',
93 'ofb' => 'nofb',
94 'ofb8' => 'ofb',
95 'cfb' => 'ncfb',
96 'cfb8' => 'cfb',
97 'ctr' => 'ctr',
98 'stream' => 'stream'
99 ),
100 'openssl' => array(
101 'cbc' => 'cbc',
102 'ecb' => 'ecb',
103 'ofb' => 'ofb',
104 'cfb' => 'cfb',
105 'cfb8' => 'cfb8',
106 'ctr' => 'ctr',
107 'stream' => '',
Andrey Andreevf4017672014-02-05 18:51:15 +0200108 'xts' => 'xts'
109 )
110 );
111
112 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200113 * List of supported HMAC algorightms
114 *
115 * name => digest size pairs
116 *
117 * @var array
118 */
119 protected $_digests = array(
120 'sha224' => 28,
121 'sha256' => 32,
122 'sha384' => 48,
123 'sha512' => 64
124 );
125
Andrey Andreev2da35502014-07-07 14:41:57 +0300126 /**
127 * mbstring.func_override flag
128 *
129 * @var bool
130 */
131 protected static $func_override;
132
Andrey Andreev818aedb2014-02-03 11:30:25 +0200133 // --------------------------------------------------------------------
134
135 /**
136 * Class constructor
137 *
138 * @param array $params Configuration parameters
139 * @return void
140 */
141 public function __construct(array $params = array())
142 {
143 $this->_drivers = array(
Andrey Andreev8e202162014-02-05 18:59:55 +0200144 'mcrypt' => defined('MCRYPT_DEV_URANDOM'),
Andrey Andreev818aedb2014-02-03 11:30:25 +0200145 // While OpenSSL is available for PHP 5.3.0, an IV parameter
146 // for the encrypt/decrypt functions is only available since 5.3.3
147 'openssl' => (is_php('5.3.3') && extension_loaded('openssl'))
148 );
149
150 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
151 {
152 return show_error('Encryption: Unable to find an available encryption driver.');
153 }
154
Andrey Andreev2da35502014-07-07 14:41:57 +0300155 isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
Andrey Andreev818aedb2014-02-03 11:30:25 +0200156 $this->initialize($params);
Andrey Andreev2da35502014-07-07 14:41:57 +0300157
158 if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200159 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200160 $this->_key = $key;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200161 }
162
163 log_message('debug', 'Encryption Class Initialized');
164 }
165
166 // --------------------------------------------------------------------
167
168 /**
169 * Initialize
170 *
171 * @param array $params Configuration parameters
172 * @return CI_Encryption
173 */
174 public function initialize(array $params)
175 {
176 if ( ! empty($params['driver']))
177 {
178 if (isset($this->_drivers[$params['driver']]))
179 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200180 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200181 {
182 $this->_driver = $params['driver'];
183 }
184 else
185 {
186 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
187 }
188 }
189 else
190 {
191 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
192 }
193 }
194
Andrey Andreev912831f2014-02-04 17:21:37 +0200195 if (empty($this->_driver))
196 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200197 $this->_driver = ($this->_drivers['openssl'] === TRUE)
198 ? 'openssl'
199 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200200
201 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
202 }
203
Andrey Andreev818aedb2014-02-03 11:30:25 +0200204 empty($params['key']) OR $this->_key = $params['key'];
205 $this->{'_'.$this->_driver.'_initialize'}($params);
206 return $this;
207 }
208
209 // --------------------------------------------------------------------
210
211 /**
212 * Initialize MCrypt
213 *
214 * @param array $params Configuration parameters
215 * @return void
216 */
217 protected function _mcrypt_initialize($params)
218 {
219 if ( ! empty($params['cipher']))
220 {
221 $params['cipher'] = strtolower($params['cipher']);
222 $this->_cipher_alias($params['cipher']);
223
224 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
225 {
226 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
227 }
228 else
229 {
230 $this->_cipher = $params['cipher'];
231 }
232 }
233
234 if ( ! empty($params['mode']))
235 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200236 $params['mode'] = strtolower($params['mode']);
237 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200238 {
239 log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.');
240 }
241 else
242 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200243 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200244 }
245 }
246
247 if (isset($this->_cipher, $this->_mode))
248 {
249 if (is_resource($this->_handle)
250 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
251 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
252 )
253 {
254 mcrypt_module_close($this->_handle);
255 }
256
257 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
258 {
259 log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
260 }
261 else
262 {
263 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
264 }
265 }
266 }
267
268 // --------------------------------------------------------------------
269
270 /**
271 * Initialize OpenSSL
272 *
273 * @param array $params Configuration parameters
274 * @return void
275 */
276 protected function _openssl_initialize($params)
277 {
278 if ( ! empty($params['cipher']))
279 {
280 $params['cipher'] = strtolower($params['cipher']);
281 $this->_cipher_alias($params['cipher']);
282 $this->_cipher = $params['cipher'];
283 }
284
285 if ( ! empty($params['mode']))
286 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200287 $params['mode'] = strtolower($params['mode']);
288 if ( ! isset($this->_modes['openssl'][$params['mode']]))
289 {
290 log_message('error', 'Encryption: OpenSSL mode '.strtotupper($params['mode']).' is not available.');
291 }
292 else
293 {
294 $this->_mode = $this->_modes['openssl'][$params['mode']];
295 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200296 }
297
298 if (isset($this->_cipher, $this->_mode))
299 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200300 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
301 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200302 ? $this->_cipher
303 : $this->_cipher.'-'.$this->_mode;
304
Andrey Andreev50ccc382014-02-04 23:30:06 +0200305 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200306 {
307 $this->_handle = NULL;
308 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
309 }
310 else
311 {
312 $this->_handle = $handle;
313 log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
314 }
315 }
316 }
317
318 // --------------------------------------------------------------------
319
320 /**
Andrey Andreev42183de2014-06-22 00:09:36 +0300321 * Create a random key
322 *
323 * @param int $length Output length
324 * @return string
325 */
326 public function create_key($length)
327 {
328 return ($this->_driver === 'mcrypt')
329 ? mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
330 : openssl_random_pseudo_bytes($length);
331 }
332
333 // --------------------------------------------------------------------
334
335 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200336 * Encrypt
337 *
338 * @param string $data Input data
339 * @param array $params Input parameters
340 * @return string
341 */
342 public function encrypt($data, array $params = NULL)
343 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200344 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200345 {
346 return FALSE;
347 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200348
Andrey Andreev2da35502014-07-07 14:41:57 +0300349 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200350
351 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
352 {
353 return FALSE;
354 }
355
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200356 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200357
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200358 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200359 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200360 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
361 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200362 }
363
364 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200365 }
366
367 // --------------------------------------------------------------------
368
369 /**
370 * Encrypt via MCrypt
371 *
372 * @param string $data Input data
373 * @param array $params Input parameters
374 * @return string
375 */
376 protected function _mcrypt_encrypt($data, $params)
377 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200378 if ( ! is_resource($params['handle']))
379 {
380 return FALSE;
381 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200382
Andrey Andreev1e83d692014-06-19 20:08:59 +0300383 // The greater-than-1 comparison is mostly a work-around for a bug,
384 // where 1 is returned for ARCFour instead of 0.
385 $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
386 ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
387 : NULL;
388
389 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200390 {
391 if ($params['handle'] !== $this->_handle)
392 {
393 mcrypt_module_close($params['handle']);
394 }
395
396 return FALSE;
397 }
398
399 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200400 // and other implementations outside of PHP.
401 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
402 {
403 $block_size = mcrypt_enc_get_block_size($params['handle']);
Andrey Andreev2da35502014-07-07 14:41:57 +0300404 $pad = $block_size - (self::strlen($data) % $block_size);
Andrey Andreevf4017672014-02-05 18:51:15 +0200405 $data .= str_repeat(chr($pad), $pad);
406 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200407
Andrey Andreeve7516b02014-02-05 13:45:31 +0200408 // Work-around for yet another strange behavior in MCrypt.
409 //
410 // When encrypting in ECB mode, the IV is ignored. Yet
411 // mcrypt_enc_get_iv_size() returns a value larger than 0
412 // even if ECB is used AND mcrypt_generic_init() complains
413 // if you don't pass an IV with length equal to the said
414 // return value.
415 //
416 // This probably would've been fine (even though still wasteful),
417 // but OpenSSL isn't that dumb and we need to make the process
418 // portable, so ...
419 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreev1e83d692014-06-19 20:08:59 +0300420 ? $iv.mcrypt_generic($params['handle'], $data)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200421 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200422
423 mcrypt_generic_deinit($params['handle']);
424 if ($params['handle'] !== $this->_handle)
425 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200426 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200427 }
428
429 return $data;
430 }
431
432 // --------------------------------------------------------------------
433
434 /**
435 * Encrypt via OpenSSL
436 *
437 * @param string $data Input data
438 * @param array $params Input parameters
439 * @return string
440 */
441 protected function _openssl_encrypt($data, $params)
442 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200443 if (empty($params['handle']))
444 {
445 return FALSE;
446 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300447
448 $iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
449 ? openssl_random_pseudo_bytes($iv_size)
450 : NULL;
Andrey Andreev29cade42014-02-04 14:05:58 +0200451
Andrey Andreev818aedb2014-02-03 11:30:25 +0200452 $data = openssl_encrypt(
453 $data,
454 $params['handle'],
455 $params['key'],
456 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300457 $iv
Andrey Andreev818aedb2014-02-03 11:30:25 +0200458 );
459
460 if ($data === FALSE)
461 {
462 return FALSE;
463 }
464
Andrey Andreev1e83d692014-06-19 20:08:59 +0300465 return $iv.$data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200466 }
467
468 // --------------------------------------------------------------------
469
470 /**
471 * Decrypt
472 *
473 * @param string $data Encrypted data
474 * @param array $params Input parameters
475 * @return string
476 */
477 public function decrypt($data, array $params = NULL)
478 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200479 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200480 {
481 return FALSE;
482 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200483
484 if (isset($params['hmac_digest']))
485 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200486 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200487 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200488 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200489 ? $this->_digests[$params['hmac_digest']] * 2
490 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200491
Andrey Andreev2da35502014-07-07 14:41:57 +0300492 if (self::strlen($data) <= $digest_size)
Andrey Andreev7c554482014-02-06 02:38:27 +0200493 {
494 return FALSE;
495 }
496
497 $hmac_input = substr($data, 0, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200498 $data = substr($data, $digest_size);
499
Andrey Andreev7c554482014-02-06 02:38:27 +0200500 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
501 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
502
503 // Time-attack-safe comparison
504 $diff = 0;
505 for ($i = 0; $i < $digest_size; $i++)
506 {
507 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
508 }
509
510 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200511 {
512 return FALSE;
513 }
514 }
515
516 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200517 {
518 $data = base64_decode($data);
519 }
520
Andrey Andreev2da35502014-07-07 14:41:57 +0300521 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev81e10642014-02-07 01:43:36 +0200522
Andrey Andreev818aedb2014-02-03 11:30:25 +0200523 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
524 }
525
526 // --------------------------------------------------------------------
527
528 /**
529 * Decrypt via MCrypt
530 *
531 * @param string $data Encrypted data
532 * @param array $params Input parameters
533 * @return string
534 */
535 protected function _mcrypt_decrypt($data, $params)
536 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200537 if ( ! is_resource($params['handle']))
538 {
539 return FALSE;
540 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300541
542 // The greater-than-1 comparison is mostly a work-around for a bug,
543 // where 1 is returned for ARCFour instead of 0.
544 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200545 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300546 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreeve7516b02014-02-05 13:45:31 +0200547 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300548 $iv = substr($data, 0, $iv_size);
549 $data = substr($data, $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200550 }
551 else
552 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300553 // MCrypt is dumb and this is ignored, only size matters
554 $iv = str_repeat("\x0", $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200555 }
556 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300557 else
558 {
559 $iv = NULL;
560 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200561
Andrey Andreev1e83d692014-06-19 20:08:59 +0300562 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200563 {
564 if ($params['handle'] !== $this->_handle)
565 {
566 mcrypt_module_close($params['handle']);
567 }
568
569 return FALSE;
570 }
571
572 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200573 // Remove PKCS#7 padding, if necessary
574 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
575 {
Andrey Andreev2da35502014-07-07 14:41:57 +0300576 $data = substr($data, 0, -ord($data[self::strlen($data)-1]));
Andrey Andreevf4017672014-02-05 18:51:15 +0200577 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200578
579 mcrypt_generic_deinit($params['handle']);
580 if ($params['handle'] !== $this->_handle)
581 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200582 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200583 }
584
Andrey Andreevf4017672014-02-05 18:51:15 +0200585 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200586 }
587
588 // --------------------------------------------------------------------
589
590 /**
591 * Decrypt via OpenSSL
592 *
593 * @param string $data Encrypted data
594 * @param array $params Input parameters
595 * @return string
596 */
597 protected function _openssl_decrypt($data, $params)
598 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300599 if ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreeve7516b02014-02-05 13:45:31 +0200600 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300601 $iv = substr($data, 0, $iv_size);
602 $data = substr($data, $iv_size);
603 }
604 else
605 {
606 $iv = NULL;
Andrey Andreeve7516b02014-02-05 13:45:31 +0200607 }
608
Andrey Andreev29cade42014-02-04 14:05:58 +0200609 return empty($params['handle'])
610 ? FALSE
611 : openssl_decrypt(
612 $data,
613 $params['handle'],
614 $params['key'],
615 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300616 $iv
Andrey Andreev29cade42014-02-04 14:05:58 +0200617 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200618 }
619
620 // --------------------------------------------------------------------
621
622 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200623 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200624 *
625 * @param array $params Input parameters
626 * @return array
627 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200628 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200629 {
630 if (empty($params))
631 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200632 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200633 ? array(
634 'handle' => $this->_handle,
635 'cipher' => $this->_cipher,
636 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200637 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200638 'base64' => TRUE,
Andrey Andreevab9971f2014-07-02 19:09:08 +0300639 'hmac_digest' => 'sha512',
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200640 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200641 )
642 : FALSE;
643 }
644 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
645 {
646 return FALSE;
647 }
648
Andrey Andreevf4017672014-02-05 18:51:15 +0200649 if (isset($params['mode']))
650 {
651 $params['mode'] = strtolower($params['mode']);
652 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
653 {
654 return FALSE;
655 }
656 else
657 {
658 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
659 }
660 }
661
Andrey Andreevab9971f2014-07-02 19:09:08 +0300662 if (isset($params['hmac']) && $params['hmac'] === FALSE)
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200663 {
664 $params['hmac_digest'] = $params['hmac_key'] = NULL;
665 }
666 else
667 {
668 if ( ! isset($params['hmac_key']))
669 {
670 return FALSE;
671 }
672 elseif (isset($params['hmac_digest']))
673 {
674 $params['hmac_digest'] = strtolower($params['hmac_digest']);
675 if ( ! isset($this->_digests[$params['hmac_digest']]))
676 {
677 return FALSE;
678 }
679 }
680 else
681 {
682 $params['hmac_digest'] = 'sha512';
683 }
684 }
685
Andrey Andreev818aedb2014-02-03 11:30:25 +0200686 $params = array(
687 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200688 'cipher' => $params['cipher'],
689 'mode' => $params['mode'],
690 'key' => $params['key'],
Andrey Andreev4b450652014-02-10 06:59:54 +0200691 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200692 'hmac_digest' => $params['hmac_digest'],
693 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200694 );
695
Andrey Andreev818aedb2014-02-03 11:30:25 +0200696 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200697 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
698 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
699 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200700
701 return $params;
702 }
703
704 // --------------------------------------------------------------------
705
706 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200707 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200708 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200709 * @param string $cipher Cipher name
710 * @param string $mode Encryption mode
711 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200712 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200713 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200714 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200715 return mcrypt_module_open($cipher, '', $mode, '');
716 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200717
Andrey Andreev29cade42014-02-04 14:05:58 +0200718 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200719
Andrey Andreev29cade42014-02-04 14:05:58 +0200720 /**
721 * Get OpenSSL handle
722 *
723 * @param string $cipher Cipher name
724 * @param string $mode Encryption mode
725 * @return string
726 */
727 protected function _openssl_get_handle($cipher, $mode)
728 {
729 // OpenSSL methods aren't suffixed with '-stream' for this mode
730 return ($mode === 'stream')
731 ? $cipher
732 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200733 }
734
735 // --------------------------------------------------------------------
736
737 /**
738 * Cipher alias
739 *
740 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
741 *
742 * @param string $cipher Cipher name
743 * @return void
744 */
745 protected function _cipher_alias(&$cipher)
746 {
747 static $dictionary;
748
749 if (empty($dictionary))
750 {
751 $dictionary = array(
752 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200753 'aes-128' => 'rijndael-128',
754 'aes-192' => 'rijndael-128',
755 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200756 'des3-ede3' => 'tripledes',
757 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200758 'cast5' => 'cast-128',
759 'rc4' => 'arcfour',
760 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200761 ),
762 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200763 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200764 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200765 'blowfish' => 'bf',
766 'cast-128' => 'cast5',
767 'arcfour' => 'rc4-40',
768 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200769 )
770 );
771
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200772 // Notes:
773 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200774 // - Rijndael-128 is, at the same time all three of AES-128,
775 // AES-192 and AES-256. The only difference between them is
776 // the key size. Rijndael-192, Rijndael-256 on the other hand
777 // also have different block sizes and are NOT AES-compatible.
778 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200779 // - Blowfish is said to be supporting key sizes between
780 // 4 and 56 bytes, but it appears that between MCrypt and
781 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200782 // Also, don't know what MCrypt's 'blowfish-compat' is.
783 //
784 // - CAST-128/CAST5 produces a longer cipher when encrypted via
785 // OpenSSL, but (strangely enough) can be decrypted by either
786 // extension anyway.
Andrey Andreev18767e32014-03-04 22:21:35 +0200787 // Also, it appears that OpenSSL uses 16 rounds regardless of
788 // the key size, while RFC2144 says that for key sizes lower
789 // than 11 bytes, only 12 rounds should be used. This makes
790 // it portable only with keys of between 11 and 16 bytes.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200791 //
792 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
793 // Its 'rc4-40' cipher method seems to work flawlessly, yet
794 // there's another one, 'rc4' that only works with a 16-byte key.
795 //
796 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200797 //
798 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200799 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200800 // - RC2 is NOT compatible and only an obscure forum post
801 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200802 }
803
Andrey Andreev50ccc382014-02-04 23:30:06 +0200804 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200805 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200806 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200807 }
808 }
809
810 // --------------------------------------------------------------------
811
812 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200813 * HKDF
814 *
815 * @link https://tools.ietf.org/rfc/rfc5869.txt
816 * @param $key Input key
817 * @param $digest A SHA-2 hashing algorithm
818 * @param $salt Optional salt
Andrey Andreev29cade42014-02-04 14:05:58 +0200819 * @param $length Output length (defaults to the selected digest size)
Andrey Andreev4b450652014-02-10 06:59:54 +0200820 * @param $info Optional context/application-specific info
Andrey Andreev29cade42014-02-04 14:05:58 +0200821 * @return string A pseudo-random key
822 */
823 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
824 {
825 if ( ! isset($this->_digests[$digest]))
826 {
827 return FALSE;
828 }
829
830 if (empty($length) OR ! is_int($length))
831 {
832 $length = $this->_digests[$digest];
833 }
834 elseif ($length > (255 * $this->_digests[$digest]))
835 {
836 return FALSE;
837 }
838
Andrey Andreev2da35502014-07-07 14:41:57 +0300839 self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
Andrey Andreev29cade42014-02-04 14:05:58 +0200840
841 $prk = hash_hmac($digest, $key, $salt, TRUE);
842 $key = '';
Andrey Andreev2da35502014-07-07 14:41:57 +0300843 for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++)
Andrey Andreev29cade42014-02-04 14:05:58 +0200844 {
845 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
846 $key .= $key_block;
847 }
848
849 return substr($key, 0, $length);
850 }
851
852 // --------------------------------------------------------------------
853
854 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200855 * __get() magic
856 *
857 * @param string $key Property name
858 * @return mixed
859 */
860 public function __get($key)
861 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200862 // Because aliases
863 if ($key === 'mode')
864 {
865 return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);
866 }
867 elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE))
868 {
869 return $this->{'_'.$key};
870 }
871
872 return NULL;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200873 }
874
Andrey Andreev2da35502014-07-07 14:41:57 +0300875 // --------------------------------------------------------------------
876
877 /**
878 * Byte-safe strlen()
879 *
880 * @param string $str
881 * @return integer
882 */
883 protected static function strlen($str)
884 {
885 return (self::$func_override)
886 ? mb_strlen($str, '8bit')
887 : strlen($str);
888 }
889
890 // --------------------------------------------------------------------
891
892 /**
893 * Byte-safe substr()
894 *
895 * @param string $str
896 * @param int $start
897 * @param int $length
898 * @return string
899 */
900 protected static function substr($str, $start, $length = null)
901 {
902 if (self::$func_override)
903 {
904 return mb_substr($str, $start, $length);
905 }
906
907 return isset($length)
908 ? substr($str, $start, $length)
909 : substr($str, $start);
910 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200911}
912
913/* End of file Encryption.php */
914/* Location: ./system/libraries/Encryption.php */