blob: aa91cd3f9c9a3a54d2e12b485e44d9b3decb68dc [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' => '',
108 'gcm' => 'gcm',
109 'xts' => 'xts'
110 )
111 );
112
113 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200114 * List of supported HMAC algorightms
115 *
116 * name => digest size pairs
117 *
118 * @var array
119 */
120 protected $_digests = array(
121 'sha224' => 28,
122 'sha256' => 32,
123 'sha384' => 48,
124 'sha512' => 64
125 );
126
Andrey Andreev818aedb2014-02-03 11:30:25 +0200127 // --------------------------------------------------------------------
128
129 /**
130 * Class constructor
131 *
132 * @param array $params Configuration parameters
133 * @return void
134 */
135 public function __construct(array $params = array())
136 {
137 $this->_drivers = array(
Andrey Andreev8e202162014-02-05 18:59:55 +0200138 'mcrypt' => defined('MCRYPT_DEV_URANDOM'),
Andrey Andreev818aedb2014-02-03 11:30:25 +0200139 // While OpenSSL is available for PHP 5.3.0, an IV parameter
140 // for the encrypt/decrypt functions is only available since 5.3.3
141 'openssl' => (is_php('5.3.3') && extension_loaded('openssl'))
142 );
143
144 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
145 {
146 return show_error('Encryption: Unable to find an available encryption driver.');
147 }
148
149 $this->initialize($params);
Andrey Andreev81e10642014-02-07 01:43:36 +0200150 if ( ! isset($this->_key) && strlen($key = config_item('encryption_key')) > 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200151 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200152 $this->_key = $key;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200153 }
154
155 log_message('debug', 'Encryption Class Initialized');
156 }
157
158 // --------------------------------------------------------------------
159
160 /**
161 * Initialize
162 *
163 * @param array $params Configuration parameters
164 * @return CI_Encryption
165 */
166 public function initialize(array $params)
167 {
168 if ( ! empty($params['driver']))
169 {
170 if (isset($this->_drivers[$params['driver']]))
171 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200172 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200173 {
174 $this->_driver = $params['driver'];
175 }
176 else
177 {
178 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
179 }
180 }
181 else
182 {
183 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
184 }
185 }
186
Andrey Andreev912831f2014-02-04 17:21:37 +0200187 if (empty($this->_driver))
188 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200189 $this->_driver = ($this->_drivers['openssl'] === TRUE)
190 ? 'openssl'
191 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200192
193 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
194 }
195
Andrey Andreev818aedb2014-02-03 11:30:25 +0200196 empty($params['key']) OR $this->_key = $params['key'];
197 $this->{'_'.$this->_driver.'_initialize'}($params);
198 return $this;
199 }
200
201 // --------------------------------------------------------------------
202
203 /**
204 * Initialize MCrypt
205 *
206 * @param array $params Configuration parameters
207 * @return void
208 */
209 protected function _mcrypt_initialize($params)
210 {
211 if ( ! empty($params['cipher']))
212 {
213 $params['cipher'] = strtolower($params['cipher']);
214 $this->_cipher_alias($params['cipher']);
215
216 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
217 {
218 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
219 }
220 else
221 {
222 $this->_cipher = $params['cipher'];
223 }
224 }
225
226 if ( ! empty($params['mode']))
227 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200228 $params['mode'] = strtolower($params['mode']);
229 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200230 {
231 log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.');
232 }
233 else
234 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200235 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200236 }
237 }
238
239 if (isset($this->_cipher, $this->_mode))
240 {
241 if (is_resource($this->_handle)
242 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
243 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
244 )
245 {
246 mcrypt_module_close($this->_handle);
247 }
248
249 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
250 {
251 log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
252 }
253 else
254 {
255 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
256 }
257 }
258 }
259
260 // --------------------------------------------------------------------
261
262 /**
263 * Initialize OpenSSL
264 *
265 * @param array $params Configuration parameters
266 * @return void
267 */
268 protected function _openssl_initialize($params)
269 {
270 if ( ! empty($params['cipher']))
271 {
272 $params['cipher'] = strtolower($params['cipher']);
273 $this->_cipher_alias($params['cipher']);
274 $this->_cipher = $params['cipher'];
275 }
276
277 if ( ! empty($params['mode']))
278 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200279 $params['mode'] = strtolower($params['mode']);
280 if ( ! isset($this->_modes['openssl'][$params['mode']]))
281 {
282 log_message('error', 'Encryption: OpenSSL mode '.strtotupper($params['mode']).' is not available.');
283 }
284 else
285 {
286 $this->_mode = $this->_modes['openssl'][$params['mode']];
287 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200288 }
289
290 if (isset($this->_cipher, $this->_mode))
291 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200292 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
293 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200294 ? $this->_cipher
295 : $this->_cipher.'-'.$this->_mode;
296
Andrey Andreev50ccc382014-02-04 23:30:06 +0200297 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200298 {
299 $this->_handle = NULL;
300 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
301 }
302 else
303 {
304 $this->_handle = $handle;
305 log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
306 }
307 }
308 }
309
310 // --------------------------------------------------------------------
311
312 /**
Andrey Andreev42183de2014-06-22 00:09:36 +0300313 * Create a random key
314 *
315 * @param int $length Output length
316 * @return string
317 */
318 public function create_key($length)
319 {
320 return ($this->_driver === 'mcrypt')
321 ? mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
322 : openssl_random_pseudo_bytes($length);
323 }
324
325 // --------------------------------------------------------------------
326
327 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200328 * Encrypt
329 *
330 * @param string $data Input data
331 * @param array $params Input parameters
332 * @return string
333 */
334 public function encrypt($data, array $params = NULL)
335 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200336 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200337 {
338 return FALSE;
339 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200340
Andrey Andreev81e10642014-02-07 01:43:36 +0200341 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200342
343 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
344 {
345 return FALSE;
346 }
347
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200348 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200349
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200350 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200351 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200352 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
353 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200354 }
355
356 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200357 }
358
359 // --------------------------------------------------------------------
360
361 /**
362 * Encrypt via MCrypt
363 *
364 * @param string $data Input data
365 * @param array $params Input parameters
366 * @return string
367 */
368 protected function _mcrypt_encrypt($data, $params)
369 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200370 if ( ! is_resource($params['handle']))
371 {
372 return FALSE;
373 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200374
Andrey Andreev1e83d692014-06-19 20:08:59 +0300375 // The greater-than-1 comparison is mostly a work-around for a bug,
376 // where 1 is returned for ARCFour instead of 0.
377 $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
378 ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
379 : NULL;
380
381 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200382 {
383 if ($params['handle'] !== $this->_handle)
384 {
385 mcrypt_module_close($params['handle']);
386 }
387
388 return FALSE;
389 }
390
391 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200392 // and other implementations outside of PHP.
393 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
394 {
395 $block_size = mcrypt_enc_get_block_size($params['handle']);
396 $pad = $block_size - (strlen($data) % $block_size);
397 $data .= str_repeat(chr($pad), $pad);
398 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200399
Andrey Andreeve7516b02014-02-05 13:45:31 +0200400 // Work-around for yet another strange behavior in MCrypt.
401 //
402 // When encrypting in ECB mode, the IV is ignored. Yet
403 // mcrypt_enc_get_iv_size() returns a value larger than 0
404 // even if ECB is used AND mcrypt_generic_init() complains
405 // if you don't pass an IV with length equal to the said
406 // return value.
407 //
408 // This probably would've been fine (even though still wasteful),
409 // but OpenSSL isn't that dumb and we need to make the process
410 // portable, so ...
411 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreev1e83d692014-06-19 20:08:59 +0300412 ? $iv.mcrypt_generic($params['handle'], $data)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200413 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200414
415 mcrypt_generic_deinit($params['handle']);
416 if ($params['handle'] !== $this->_handle)
417 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200418 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200419 }
420
421 return $data;
422 }
423
424 // --------------------------------------------------------------------
425
426 /**
427 * Encrypt via OpenSSL
428 *
429 * @param string $data Input data
430 * @param array $params Input parameters
431 * @return string
432 */
433 protected function _openssl_encrypt($data, $params)
434 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200435 if (empty($params['handle']))
436 {
437 return FALSE;
438 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300439
440 $iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
441 ? openssl_random_pseudo_bytes($iv_size)
442 : NULL;
Andrey Andreev29cade42014-02-04 14:05:58 +0200443
Andrey Andreev818aedb2014-02-03 11:30:25 +0200444 $data = openssl_encrypt(
445 $data,
446 $params['handle'],
447 $params['key'],
448 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300449 $iv
Andrey Andreev818aedb2014-02-03 11:30:25 +0200450 );
451
452 if ($data === FALSE)
453 {
454 return FALSE;
455 }
456
Andrey Andreev1e83d692014-06-19 20:08:59 +0300457 return $iv.$data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200458 }
459
460 // --------------------------------------------------------------------
461
462 /**
463 * Decrypt
464 *
465 * @param string $data Encrypted data
466 * @param array $params Input parameters
467 * @return string
468 */
469 public function decrypt($data, array $params = NULL)
470 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200471 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200472 {
473 return FALSE;
474 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200475
476 if (isset($params['hmac_digest']))
477 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200478 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200479 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200480 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200481 ? $this->_digests[$params['hmac_digest']] * 2
482 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200483
484 if (strlen($data) <= $digest_size)
485 {
486 return FALSE;
487 }
488
489 $hmac_input = substr($data, 0, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200490 $data = substr($data, $digest_size);
491
Andrey Andreev7c554482014-02-06 02:38:27 +0200492 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
493 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
494
495 // Time-attack-safe comparison
496 $diff = 0;
497 for ($i = 0; $i < $digest_size; $i++)
498 {
499 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
500 }
501
502 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200503 {
504 return FALSE;
505 }
506 }
507
508 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200509 {
510 $data = base64_decode($data);
511 }
512
Andrey Andreev81e10642014-02-07 01:43:36 +0200513 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
514
Andrey Andreev818aedb2014-02-03 11:30:25 +0200515 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
516 }
517
518 // --------------------------------------------------------------------
519
520 /**
521 * Decrypt via MCrypt
522 *
523 * @param string $data Encrypted data
524 * @param array $params Input parameters
525 * @return string
526 */
527 protected function _mcrypt_decrypt($data, $params)
528 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200529 if ( ! is_resource($params['handle']))
530 {
531 return FALSE;
532 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300533
534 // The greater-than-1 comparison is mostly a work-around for a bug,
535 // where 1 is returned for ARCFour instead of 0.
536 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200537 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300538 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreeve7516b02014-02-05 13:45:31 +0200539 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300540 $iv = substr($data, 0, $iv_size);
541 $data = substr($data, $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200542 }
543 else
544 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300545 // MCrypt is dumb and this is ignored, only size matters
546 $iv = str_repeat("\x0", $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200547 }
548 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300549 else
550 {
551 $iv = NULL;
552 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200553
Andrey Andreev1e83d692014-06-19 20:08:59 +0300554 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200555 {
556 if ($params['handle'] !== $this->_handle)
557 {
558 mcrypt_module_close($params['handle']);
559 }
560
561 return FALSE;
562 }
563
564 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200565 // Remove PKCS#7 padding, if necessary
566 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
567 {
568 $data = substr($data, 0, -ord($data[strlen($data)-1]));
569 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200570
571 mcrypt_generic_deinit($params['handle']);
572 if ($params['handle'] !== $this->_handle)
573 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200574 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200575 }
576
Andrey Andreevf4017672014-02-05 18:51:15 +0200577 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200578 }
579
580 // --------------------------------------------------------------------
581
582 /**
583 * Decrypt via OpenSSL
584 *
585 * @param string $data Encrypted data
586 * @param array $params Input parameters
587 * @return string
588 */
589 protected function _openssl_decrypt($data, $params)
590 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300591 if ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreeve7516b02014-02-05 13:45:31 +0200592 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300593 $iv = substr($data, 0, $iv_size);
594 $data = substr($data, $iv_size);
595 }
596 else
597 {
598 $iv = NULL;
Andrey Andreeve7516b02014-02-05 13:45:31 +0200599 }
600
Andrey Andreev29cade42014-02-04 14:05:58 +0200601 return empty($params['handle'])
602 ? FALSE
603 : openssl_decrypt(
604 $data,
605 $params['handle'],
606 $params['key'],
607 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300608 $iv
Andrey Andreev29cade42014-02-04 14:05:58 +0200609 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200610 }
611
612 // --------------------------------------------------------------------
613
614 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200615 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200616 *
617 * @param array $params Input parameters
618 * @return array
619 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200620 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200621 {
622 if (empty($params))
623 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200624 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200625 ? array(
626 'handle' => $this->_handle,
627 'cipher' => $this->_cipher,
628 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200629 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200630 'base64' => TRUE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200631 'hmac_digest' => ($this->_mode !== 'gcm' ? 'sha512' : NULL),
632 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200633 )
634 : FALSE;
635 }
636 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
637 {
638 return FALSE;
639 }
640
Andrey Andreevf4017672014-02-05 18:51:15 +0200641 if (isset($params['mode']))
642 {
643 $params['mode'] = strtolower($params['mode']);
644 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
645 {
646 return FALSE;
647 }
648 else
649 {
650 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
651 }
652 }
653
Andrey Andreev81e10642014-02-07 01:43:36 +0200654 if ($params['mode'] === 'gcm' OR (isset($params['hmac']) && $params['hmac'] === FALSE))
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200655 {
656 $params['hmac_digest'] = $params['hmac_key'] = NULL;
657 }
658 else
659 {
660 if ( ! isset($params['hmac_key']))
661 {
662 return FALSE;
663 }
664 elseif (isset($params['hmac_digest']))
665 {
666 $params['hmac_digest'] = strtolower($params['hmac_digest']);
667 if ( ! isset($this->_digests[$params['hmac_digest']]))
668 {
669 return FALSE;
670 }
671 }
672 else
673 {
674 $params['hmac_digest'] = 'sha512';
675 }
676 }
677
Andrey Andreev818aedb2014-02-03 11:30:25 +0200678 $params = array(
679 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200680 'cipher' => $params['cipher'],
681 'mode' => $params['mode'],
682 'key' => $params['key'],
Andrey Andreev4b450652014-02-10 06:59:54 +0200683 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200684 'hmac_digest' => $params['hmac_digest'],
685 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200686 );
687
Andrey Andreev818aedb2014-02-03 11:30:25 +0200688 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200689 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
690 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
691 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200692
693 return $params;
694 }
695
696 // --------------------------------------------------------------------
697
698 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200699 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200700 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200701 * @param string $cipher Cipher name
702 * @param string $mode Encryption mode
703 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200704 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200705 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200706 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200707 return mcrypt_module_open($cipher, '', $mode, '');
708 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200709
Andrey Andreev29cade42014-02-04 14:05:58 +0200710 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200711
Andrey Andreev29cade42014-02-04 14:05:58 +0200712 /**
713 * Get OpenSSL handle
714 *
715 * @param string $cipher Cipher name
716 * @param string $mode Encryption mode
717 * @return string
718 */
719 protected function _openssl_get_handle($cipher, $mode)
720 {
721 // OpenSSL methods aren't suffixed with '-stream' for this mode
722 return ($mode === 'stream')
723 ? $cipher
724 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200725 }
726
727 // --------------------------------------------------------------------
728
729 /**
730 * Cipher alias
731 *
732 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
733 *
734 * @param string $cipher Cipher name
735 * @return void
736 */
737 protected function _cipher_alias(&$cipher)
738 {
739 static $dictionary;
740
741 if (empty($dictionary))
742 {
743 $dictionary = array(
744 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200745 'aes-128' => 'rijndael-128',
746 'aes-192' => 'rijndael-128',
747 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200748 'des3-ede3' => 'tripledes',
749 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200750 'cast5' => 'cast-128',
751 'rc4' => 'arcfour',
752 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200753 ),
754 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200755 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200756 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200757 'blowfish' => 'bf',
758 'cast-128' => 'cast5',
759 'arcfour' => 'rc4-40',
760 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200761 )
762 );
763
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200764 // Notes:
765 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200766 // - Rijndael-128 is, at the same time all three of AES-128,
767 // AES-192 and AES-256. The only difference between them is
768 // the key size. Rijndael-192, Rijndael-256 on the other hand
769 // also have different block sizes and are NOT AES-compatible.
770 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200771 // - Blowfish is said to be supporting key sizes between
772 // 4 and 56 bytes, but it appears that between MCrypt and
773 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200774 // Also, don't know what MCrypt's 'blowfish-compat' is.
775 //
776 // - CAST-128/CAST5 produces a longer cipher when encrypted via
777 // OpenSSL, but (strangely enough) can be decrypted by either
778 // extension anyway.
Andrey Andreev18767e32014-03-04 22:21:35 +0200779 // Also, it appears that OpenSSL uses 16 rounds regardless of
780 // the key size, while RFC2144 says that for key sizes lower
781 // than 11 bytes, only 12 rounds should be used. This makes
782 // it portable only with keys of between 11 and 16 bytes.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200783 //
784 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
785 // Its 'rc4-40' cipher method seems to work flawlessly, yet
786 // there's another one, 'rc4' that only works with a 16-byte key.
787 //
788 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200789 //
790 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200791 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200792 // - RC2 is NOT compatible and only an obscure forum post
793 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200794 }
795
Andrey Andreev50ccc382014-02-04 23:30:06 +0200796 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200797 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200798 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200799 }
800 }
801
802 // --------------------------------------------------------------------
803
804 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200805 * HKDF
806 *
807 * @link https://tools.ietf.org/rfc/rfc5869.txt
808 * @param $key Input key
809 * @param $digest A SHA-2 hashing algorithm
810 * @param $salt Optional salt
Andrey Andreev29cade42014-02-04 14:05:58 +0200811 * @param $length Output length (defaults to the selected digest size)
Andrey Andreev4b450652014-02-10 06:59:54 +0200812 * @param $info Optional context/application-specific info
Andrey Andreev29cade42014-02-04 14:05:58 +0200813 * @return string A pseudo-random key
814 */
815 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
816 {
817 if ( ! isset($this->_digests[$digest]))
818 {
819 return FALSE;
820 }
821
822 if (empty($length) OR ! is_int($length))
823 {
824 $length = $this->_digests[$digest];
825 }
826 elseif ($length > (255 * $this->_digests[$digest]))
827 {
828 return FALSE;
829 }
830
Andrey Andreeva4f113e2014-02-18 21:18:31 +0200831 strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
Andrey Andreev29cade42014-02-04 14:05:58 +0200832
833 $prk = hash_hmac($digest, $key, $salt, TRUE);
834 $key = '';
835 for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
836 {
837 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
838 $key .= $key_block;
839 }
840
841 return substr($key, 0, $length);
842 }
843
844 // --------------------------------------------------------------------
845
846 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200847 * __get() magic
848 *
849 * @param string $key Property name
850 * @return mixed
851 */
852 public function __get($key)
853 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200854 // Because aliases
855 if ($key === 'mode')
856 {
857 return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);
858 }
859 elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE))
860 {
861 return $this->{'_'.$key};
862 }
863
864 return NULL;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200865 }
866
867}
868
869/* End of file Encryption.php */
870/* Location: ./system/libraries/Encryption.php */