blob: f5950ee46f531b1d0319709f2335fec964ab7a8a [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
21 * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
22 * @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 */
47 protected $_cipher = 'rijndael-128';
48
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);
150
Andrey Andreev818aedb2014-02-03 11:30:25 +0200151 isset($this->_key) OR $this->_key = config_item('encryption_key');
152 if (empty($this->_key))
153 {
154 return show_error('Encryption: You are required to set an encryption key in your configuration.');
155 }
156
157 log_message('debug', 'Encryption Class Initialized');
158 }
159
160 // --------------------------------------------------------------------
161
162 /**
163 * Initialize
164 *
165 * @param array $params Configuration parameters
166 * @return CI_Encryption
167 */
168 public function initialize(array $params)
169 {
170 if ( ! empty($params['driver']))
171 {
172 if (isset($this->_drivers[$params['driver']]))
173 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200174 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200175 {
176 $this->_driver = $params['driver'];
177 }
178 else
179 {
180 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
181 }
182 }
183 else
184 {
185 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
186 }
187 }
188
Andrey Andreev912831f2014-02-04 17:21:37 +0200189 if (empty($this->_driver))
190 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200191 $this->_driver = ($this->_drivers['openssl'] === TRUE)
192 ? 'openssl'
193 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200194
195 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
196 }
197
Andrey Andreev818aedb2014-02-03 11:30:25 +0200198 empty($params['key']) OR $this->_key = $params['key'];
199 $this->{'_'.$this->_driver.'_initialize'}($params);
200 return $this;
201 }
202
203 // --------------------------------------------------------------------
204
205 /**
206 * Initialize MCrypt
207 *
208 * @param array $params Configuration parameters
209 * @return void
210 */
211 protected function _mcrypt_initialize($params)
212 {
213 if ( ! empty($params['cipher']))
214 {
215 $params['cipher'] = strtolower($params['cipher']);
216 $this->_cipher_alias($params['cipher']);
217
218 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
219 {
220 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
221 }
222 else
223 {
224 $this->_cipher = $params['cipher'];
225 }
226 }
227
228 if ( ! empty($params['mode']))
229 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200230 $params['mode'] = strtolower($params['mode']);
231 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200232 {
233 log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.');
234 }
235 else
236 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200237 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200238 }
239 }
240
241 if (isset($this->_cipher, $this->_mode))
242 {
243 if (is_resource($this->_handle)
244 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
245 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
246 )
247 {
248 mcrypt_module_close($this->_handle);
249 }
250
251 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
252 {
253 log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
254 }
255 else
256 {
257 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
258 }
259 }
260 }
261
262 // --------------------------------------------------------------------
263
264 /**
265 * Initialize OpenSSL
266 *
267 * @param array $params Configuration parameters
268 * @return void
269 */
270 protected function _openssl_initialize($params)
271 {
272 if ( ! empty($params['cipher']))
273 {
274 $params['cipher'] = strtolower($params['cipher']);
275 $this->_cipher_alias($params['cipher']);
276 $this->_cipher = $params['cipher'];
277 }
278
279 if ( ! empty($params['mode']))
280 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200281 $params['mode'] = strtolower($params['mode']);
282 if ( ! isset($this->_modes['openssl'][$params['mode']]))
283 {
284 log_message('error', 'Encryption: OpenSSL mode '.strtotupper($params['mode']).' is not available.');
285 }
286 else
287 {
288 $this->_mode = $this->_modes['openssl'][$params['mode']];
289 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200290 }
291
292 if (isset($this->_cipher, $this->_mode))
293 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200294 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
295 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200296 ? $this->_cipher
297 : $this->_cipher.'-'.$this->_mode;
298
Andrey Andreev50ccc382014-02-04 23:30:06 +0200299 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200300 {
301 $this->_handle = NULL;
302 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
303 }
304 else
305 {
306 $this->_handle = $handle;
307 log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
308 }
309 }
310 }
311
312 // --------------------------------------------------------------------
313
314 /**
315 * Encrypt
316 *
317 * @param string $data Input data
318 * @param array $params Input parameters
319 * @return string
320 */
321 public function encrypt($data, array $params = NULL)
322 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200323 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200324 {
325 return FALSE;
326 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200327 elseif ( ! isset($params['key']))
328 {
329 if ( ! isset($this->_key))
330 {
331 return show_error('Encryption: You are required to set an encryption key in your configuration.');
332 }
333
334 $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
335 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200336
337 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
338 {
339 return FALSE;
340 }
341
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200342 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200343
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200344 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200345 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200346 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
347 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200348 }
349
350 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200351 }
352
353 // --------------------------------------------------------------------
354
355 /**
356 * Encrypt via MCrypt
357 *
358 * @param string $data Input data
359 * @param array $params Input parameters
360 * @return string
361 */
362 protected function _mcrypt_encrypt($data, $params)
363 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200364 if ( ! is_resource($params['handle']))
365 {
366 return FALSE;
367 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200368 elseif ( ! isset($params['iv']))
369 {
370 $params['iv'] = ($iv_size = mcrypt_enc_get_iv_size($params['handle']))
Andrey Andreev8e202162014-02-05 18:59:55 +0200371 ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200372 : NULL;
373 }
374
375
376 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200377 {
378 if ($params['handle'] !== $this->_handle)
379 {
380 mcrypt_module_close($params['handle']);
381 }
382
383 return FALSE;
384 }
385
386 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200387 // and other implementations outside of PHP.
388 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
389 {
390 $block_size = mcrypt_enc_get_block_size($params['handle']);
391 $pad = $block_size - (strlen($data) % $block_size);
392 $data .= str_repeat(chr($pad), $pad);
393 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200394
Andrey Andreeve7516b02014-02-05 13:45:31 +0200395 // Work-around for yet another strange behavior in MCrypt.
396 //
397 // When encrypting in ECB mode, the IV is ignored. Yet
398 // mcrypt_enc_get_iv_size() returns a value larger than 0
399 // even if ECB is used AND mcrypt_generic_init() complains
400 // if you don't pass an IV with length equal to the said
401 // return value.
402 //
403 // This probably would've been fine (even though still wasteful),
404 // but OpenSSL isn't that dumb and we need to make the process
405 // portable, so ...
406 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
407 ? $params['iv'].mcrypt_generic($params['handle'], $data)
408 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200409
410 mcrypt_generic_deinit($params['handle']);
411 if ($params['handle'] !== $this->_handle)
412 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200413 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200414 }
415
416 return $data;
417 }
418
419 // --------------------------------------------------------------------
420
421 /**
422 * Encrypt via OpenSSL
423 *
424 * @param string $data Input data
425 * @param array $params Input parameters
426 * @return string
427 */
428 protected function _openssl_encrypt($data, $params)
429 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200430 if (empty($params['handle']))
431 {
432 return FALSE;
433 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200434 elseif ( ! isset($params['iv']))
435 {
436 $params['iv'] = ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreev8e202162014-02-05 18:59:55 +0200437 ? openssl_random_pseudo_bytes($iv_size)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200438 : NULL;
439 }
Andrey Andreev29cade42014-02-04 14:05:58 +0200440
Andrey Andreev818aedb2014-02-03 11:30:25 +0200441 $data = openssl_encrypt(
442 $data,
443 $params['handle'],
444 $params['key'],
445 1, // DO NOT TOUCH!
446 $params['iv']
447 );
448
449 if ($data === FALSE)
450 {
451 return FALSE;
452 }
453
454 return $params['iv'].$data;
455 }
456
457 // --------------------------------------------------------------------
458
459 /**
460 * Decrypt
461 *
462 * @param string $data Encrypted data
463 * @param array $params Input parameters
464 * @return string
465 */
466 public function decrypt($data, array $params = NULL)
467 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200468 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200469 {
470 return FALSE;
471 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200472 elseif ( ! isset($params['key']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200473 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200474 if ( ! isset($this->_key))
Andrey Andreev29cade42014-02-04 14:05:58 +0200475 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200476 return show_error('Encryption: You are required to set an encryption key in your configuration.');
Andrey Andreev29cade42014-02-04 14:05:58 +0200477 }
478
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200479 $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
480 }
481
482 if (isset($params['hmac_digest']))
483 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200484 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200485 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200486 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200487 ? $this->_digests[$params['hmac_digest']] * 2
488 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200489
490 if (strlen($data) <= $digest_size)
491 {
492 return FALSE;
493 }
494
495 $hmac_input = substr($data, 0, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200496 $data = substr($data, $digest_size);
497
Andrey Andreev7c554482014-02-06 02:38:27 +0200498 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
499 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
500
501 // Time-attack-safe comparison
502 $diff = 0;
503 for ($i = 0; $i < $digest_size; $i++)
504 {
505 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
506 }
507
508 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200509 {
510 return FALSE;
511 }
512 }
513
514 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200515 {
516 $data = base64_decode($data);
517 }
518
Andrey Andreeve7516b02014-02-05 13:45:31 +0200519 if (isset($params['iv']) && strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200520 {
521 $data = substr($data, $iv_size);
522 }
523
524 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
525 }
526
527 // --------------------------------------------------------------------
528
529 /**
530 * Decrypt via MCrypt
531 *
532 * @param string $data Encrypted data
533 * @param array $params Input parameters
534 * @return string
535 */
536 protected function _mcrypt_decrypt($data, $params)
537 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200538 if ( ! is_resource($params['handle']))
539 {
540 return FALSE;
541 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200542 elseif ( ! isset($params['iv']))
543 {
544 if ($iv_size = mcrypt_enc_get_iv_size($params['handle']))
545 {
546 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
547 {
548 $params['iv'] = substr($data, 0, $iv_size);
549 $data = substr($data, $iv_size);
550 }
551 else
552 {
553 // MCrypt is dumb and this is ignored, only size matters
554 $params['iv'] = str_repeat("\x0", $iv_size);
555 }
556 }
557 else
558 {
559 $params['iv'] = NULL;
560 }
561 }
562
563 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200564 {
565 if ($params['handle'] !== $this->_handle)
566 {
567 mcrypt_module_close($params['handle']);
568 }
569
570 return FALSE;
571 }
572
573 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200574 // Remove PKCS#7 padding, if necessary
575 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
576 {
577 $data = substr($data, 0, -ord($data[strlen($data)-1]));
578 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200579
580 mcrypt_generic_deinit($params['handle']);
581 if ($params['handle'] !== $this->_handle)
582 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200583 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200584 }
585
Andrey Andreevf4017672014-02-05 18:51:15 +0200586 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200587 }
588
589 // --------------------------------------------------------------------
590
591 /**
592 * Decrypt via OpenSSL
593 *
594 * @param string $data Encrypted data
595 * @param array $params Input parameters
596 * @return string
597 */
598 protected function _openssl_decrypt($data, $params)
599 {
Andrey Andreeve7516b02014-02-05 13:45:31 +0200600 if ( ! isset($params['iv']))
601 {
602 if ($iv_size = openssl_cipher_iv_length($params['handle']))
603 {
604 $params['iv'] = substr($data, 0, $iv_size);
605 $data = substr($data, $iv_size);
606 }
607 else
608 {
609 $params['iv'] = NULL;
610 }
611 }
612
Andrey Andreev29cade42014-02-04 14:05:58 +0200613 return empty($params['handle'])
614 ? FALSE
615 : openssl_decrypt(
616 $data,
617 $params['handle'],
618 $params['key'],
619 1, // DO NOT TOUCH!
620 $params['iv']
621 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200622 }
623
624 // --------------------------------------------------------------------
625
626 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200627 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200628 *
629 * @param array $params Input parameters
630 * @return array
631 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200632 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200633 {
634 if (empty($params))
635 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200636 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200637 ? array(
638 'handle' => $this->_handle,
639 'cipher' => $this->_cipher,
640 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200641 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200642 'base64' => TRUE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200643 'hmac_digest' => ($this->_mode !== 'gcm' ? 'sha512' : NULL),
644 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200645 )
646 : FALSE;
647 }
648 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
649 {
650 return FALSE;
651 }
652
Andrey Andreevf4017672014-02-05 18:51:15 +0200653 if (isset($params['mode']))
654 {
655 $params['mode'] = strtolower($params['mode']);
656 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
657 {
658 return FALSE;
659 }
660 else
661 {
662 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
663 }
664 }
665
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200666 if ($params['mode'] === 'gcm' OR isset($params['hmac']) && $params['hmac'] === FALSE)
667 {
668 $params['hmac_digest'] = $params['hmac_key'] = NULL;
669 }
670 else
671 {
672 if ( ! isset($params['hmac_key']))
673 {
674 return FALSE;
675 }
676 elseif (isset($params['hmac_digest']))
677 {
678 $params['hmac_digest'] = strtolower($params['hmac_digest']);
679 if ( ! isset($this->_digests[$params['hmac_digest']]))
680 {
681 return FALSE;
682 }
683 }
684 else
685 {
686 $params['hmac_digest'] = 'sha512';
687 }
688 }
689
Andrey Andreev818aedb2014-02-03 11:30:25 +0200690 $params = array(
691 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200692 'cipher' => $params['cipher'],
693 'mode' => $params['mode'],
694 'key' => $params['key'],
Andrey Andreev818aedb2014-02-03 11:30:25 +0200695 'iv' => isset($params['iv']) ? $params['iv'] : NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200696 'base64' => isset($params['base64']) ? $params['base64'] : TRUE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200697 'hmac_digest' => $params['hmac_digest'],
698 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200699 );
700
Andrey Andreev818aedb2014-02-03 11:30:25 +0200701 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200702 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
703 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
704 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200705
706 return $params;
707 }
708
709 // --------------------------------------------------------------------
710
711 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200712 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200713 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200714 * @param string $cipher Cipher name
715 * @param string $mode Encryption mode
716 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200717 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200718 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200719 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200720 return mcrypt_module_open($cipher, '', $mode, '');
721 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200722
Andrey Andreev29cade42014-02-04 14:05:58 +0200723 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200724
Andrey Andreev29cade42014-02-04 14:05:58 +0200725 /**
726 * Get OpenSSL handle
727 *
728 * @param string $cipher Cipher name
729 * @param string $mode Encryption mode
730 * @return string
731 */
732 protected function _openssl_get_handle($cipher, $mode)
733 {
734 // OpenSSL methods aren't suffixed with '-stream' for this mode
735 return ($mode === 'stream')
736 ? $cipher
737 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200738 }
739
740 // --------------------------------------------------------------------
741
742 /**
743 * Cipher alias
744 *
745 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
746 *
747 * @param string $cipher Cipher name
748 * @return void
749 */
750 protected function _cipher_alias(&$cipher)
751 {
752 static $dictionary;
753
754 if (empty($dictionary))
755 {
756 $dictionary = array(
757 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200758 'aes-128' => 'rijndael-128',
759 'aes-192' => 'rijndael-128',
760 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200761 'des3-ede3' => 'tripledes',
762 'bf' => 'blowfish',
Andrey Andreev818aedb2014-02-03 11:30:25 +0200763 ),
764 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200765 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200766 'tripledes' => 'des-ede3',
767 'blowfish' => 'bf'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200768 )
769 );
770
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200771 // Notes:
772 //
773 // - Blowfish is said to be supporting key sizes between
774 // 4 and 56 bytes, but it appears that between MCrypt and
775 // OpenSSL, only those of 16 and more bytes are compatible.
776 //
777 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200778 //
779 // - DES is compatible, but doesn't need an alias
Andrey Andreev818aedb2014-02-03 11:30:25 +0200780 // - CAST-128/CAST5 is NOT compatible
781 // mcrypt: 'cast-128'
782 // openssl: 'cast5'
783 // - RC2 is NOT compatible
784 // mcrypt: 'rc2'
785 // openssl: 'rc2', 'rc2-40', 'rc2-64'
786 //
787 // To avoid any other confusion due to a popular (but incorrect)
788 // belief, it should also be noted that Rijndael-192/256 are NOT
789 // the same ciphers as AES-192/256 like Rijndael-128 and AES-256 is.
790 //
791 // All compatibility tests were done in CBC mode.
792 }
793
Andrey Andreev50ccc382014-02-04 23:30:06 +0200794 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200795 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200796 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200797 }
798 }
799
800 // --------------------------------------------------------------------
801
802 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200803 * HKDF
804 *
805 * @link https://tools.ietf.org/rfc/rfc5869.txt
806 * @param $key Input key
807 * @param $digest A SHA-2 hashing algorithm
808 * @param $salt Optional salt
809 * @param $info Optional context/application-specific info
810 * @param $length Output length (defaults to the selected digest size)
811 * @return string A pseudo-random key
812 */
813 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
814 {
815 if ( ! isset($this->_digests[$digest]))
816 {
817 return FALSE;
818 }
819
820 if (empty($length) OR ! is_int($length))
821 {
822 $length = $this->_digests[$digest];
823 }
824 elseif ($length > (255 * $this->_digests[$digest]))
825 {
826 return FALSE;
827 }
828
829 isset($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
830
831 $prk = hash_hmac($digest, $key, $salt, TRUE);
832 $key = '';
833 for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
834 {
835 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
836 $key .= $key_block;
837 }
838
839 return substr($key, 0, $length);
840 }
841
842 // --------------------------------------------------------------------
843
844 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200845 * __get() magic
846 *
847 * @param string $key Property name
848 * @return mixed
849 */
850 public function __get($key)
851 {
Andrey Andreev912831f2014-02-04 17:21:37 +0200852 return in_array($key, array('cipher', 'mode', 'driver', 'drivers', 'digests'), TRUE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200853 ? $this->{'_'.$key}
854 : NULL;
855 }
856
857}
858
859/* End of file Encryption.php */
860/* Location: ./system/libraries/Encryption.php */