blob: b85d7da36c1aa1c37136c7865bb55092bb81b2ce [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 Andreev818aedb2014-02-03 11:30:25 +0200126 // --------------------------------------------------------------------
127
128 /**
129 * Class constructor
130 *
131 * @param array $params Configuration parameters
132 * @return void
133 */
134 public function __construct(array $params = array())
135 {
136 $this->_drivers = array(
Andrey Andreev8e202162014-02-05 18:59:55 +0200137 'mcrypt' => defined('MCRYPT_DEV_URANDOM'),
Andrey Andreev818aedb2014-02-03 11:30:25 +0200138 // While OpenSSL is available for PHP 5.3.0, an IV parameter
139 // for the encrypt/decrypt functions is only available since 5.3.3
140 'openssl' => (is_php('5.3.3') && extension_loaded('openssl'))
141 );
142
143 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
144 {
145 return show_error('Encryption: Unable to find an available encryption driver.');
146 }
147
148 $this->initialize($params);
Andrey Andreev81e10642014-02-07 01:43:36 +0200149 if ( ! isset($this->_key) && strlen($key = config_item('encryption_key')) > 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200150 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200151 $this->_key = $key;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200152 }
153
154 log_message('debug', 'Encryption Class Initialized');
155 }
156
157 // --------------------------------------------------------------------
158
159 /**
160 * Initialize
161 *
162 * @param array $params Configuration parameters
163 * @return CI_Encryption
164 */
165 public function initialize(array $params)
166 {
167 if ( ! empty($params['driver']))
168 {
169 if (isset($this->_drivers[$params['driver']]))
170 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200171 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200172 {
173 $this->_driver = $params['driver'];
174 }
175 else
176 {
177 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
178 }
179 }
180 else
181 {
182 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
183 }
184 }
185
Andrey Andreev912831f2014-02-04 17:21:37 +0200186 if (empty($this->_driver))
187 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200188 $this->_driver = ($this->_drivers['openssl'] === TRUE)
189 ? 'openssl'
190 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200191
192 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
193 }
194
Andrey Andreev818aedb2014-02-03 11:30:25 +0200195 empty($params['key']) OR $this->_key = $params['key'];
196 $this->{'_'.$this->_driver.'_initialize'}($params);
197 return $this;
198 }
199
200 // --------------------------------------------------------------------
201
202 /**
203 * Initialize MCrypt
204 *
205 * @param array $params Configuration parameters
206 * @return void
207 */
208 protected function _mcrypt_initialize($params)
209 {
210 if ( ! empty($params['cipher']))
211 {
212 $params['cipher'] = strtolower($params['cipher']);
213 $this->_cipher_alias($params['cipher']);
214
215 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
216 {
217 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
218 }
219 else
220 {
221 $this->_cipher = $params['cipher'];
222 }
223 }
224
225 if ( ! empty($params['mode']))
226 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200227 $params['mode'] = strtolower($params['mode']);
228 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200229 {
230 log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.');
231 }
232 else
233 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200234 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200235 }
236 }
237
238 if (isset($this->_cipher, $this->_mode))
239 {
240 if (is_resource($this->_handle)
241 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
242 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
243 )
244 {
245 mcrypt_module_close($this->_handle);
246 }
247
248 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
249 {
250 log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
251 }
252 else
253 {
254 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
255 }
256 }
257 }
258
259 // --------------------------------------------------------------------
260
261 /**
262 * Initialize OpenSSL
263 *
264 * @param array $params Configuration parameters
265 * @return void
266 */
267 protected function _openssl_initialize($params)
268 {
269 if ( ! empty($params['cipher']))
270 {
271 $params['cipher'] = strtolower($params['cipher']);
272 $this->_cipher_alias($params['cipher']);
273 $this->_cipher = $params['cipher'];
274 }
275
276 if ( ! empty($params['mode']))
277 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200278 $params['mode'] = strtolower($params['mode']);
279 if ( ! isset($this->_modes['openssl'][$params['mode']]))
280 {
281 log_message('error', 'Encryption: OpenSSL mode '.strtotupper($params['mode']).' is not available.');
282 }
283 else
284 {
285 $this->_mode = $this->_modes['openssl'][$params['mode']];
286 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200287 }
288
289 if (isset($this->_cipher, $this->_mode))
290 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200291 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
292 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200293 ? $this->_cipher
294 : $this->_cipher.'-'.$this->_mode;
295
Andrey Andreev50ccc382014-02-04 23:30:06 +0200296 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200297 {
298 $this->_handle = NULL;
299 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
300 }
301 else
302 {
303 $this->_handle = $handle;
304 log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
305 }
306 }
307 }
308
309 // --------------------------------------------------------------------
310
311 /**
Andrey Andreev42183de2014-06-22 00:09:36 +0300312 * Create a random key
313 *
314 * @param int $length Output length
315 * @return string
316 */
317 public function create_key($length)
318 {
319 return ($this->_driver === 'mcrypt')
320 ? mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
321 : openssl_random_pseudo_bytes($length);
322 }
323
324 // --------------------------------------------------------------------
325
326 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200327 * Encrypt
328 *
329 * @param string $data Input data
330 * @param array $params Input parameters
331 * @return string
332 */
333 public function encrypt($data, array $params = NULL)
334 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200335 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200336 {
337 return FALSE;
338 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200339
Andrey Andreev81e10642014-02-07 01:43:36 +0200340 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200341
342 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
343 {
344 return FALSE;
345 }
346
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200347 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200348
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200349 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200350 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200351 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
352 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200353 }
354
355 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200356 }
357
358 // --------------------------------------------------------------------
359
360 /**
361 * Encrypt via MCrypt
362 *
363 * @param string $data Input data
364 * @param array $params Input parameters
365 * @return string
366 */
367 protected function _mcrypt_encrypt($data, $params)
368 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200369 if ( ! is_resource($params['handle']))
370 {
371 return FALSE;
372 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200373
Andrey Andreev1e83d692014-06-19 20:08:59 +0300374 // The greater-than-1 comparison is mostly a work-around for a bug,
375 // where 1 is returned for ARCFour instead of 0.
376 $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
377 ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
378 : NULL;
379
380 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200381 {
382 if ($params['handle'] !== $this->_handle)
383 {
384 mcrypt_module_close($params['handle']);
385 }
386
387 return FALSE;
388 }
389
390 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200391 // and other implementations outside of PHP.
392 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
393 {
394 $block_size = mcrypt_enc_get_block_size($params['handle']);
395 $pad = $block_size - (strlen($data) % $block_size);
396 $data .= str_repeat(chr($pad), $pad);
397 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200398
Andrey Andreeve7516b02014-02-05 13:45:31 +0200399 // Work-around for yet another strange behavior in MCrypt.
400 //
401 // When encrypting in ECB mode, the IV is ignored. Yet
402 // mcrypt_enc_get_iv_size() returns a value larger than 0
403 // even if ECB is used AND mcrypt_generic_init() complains
404 // if you don't pass an IV with length equal to the said
405 // return value.
406 //
407 // This probably would've been fine (even though still wasteful),
408 // but OpenSSL isn't that dumb and we need to make the process
409 // portable, so ...
410 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreev1e83d692014-06-19 20:08:59 +0300411 ? $iv.mcrypt_generic($params['handle'], $data)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200412 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200413
414 mcrypt_generic_deinit($params['handle']);
415 if ($params['handle'] !== $this->_handle)
416 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200417 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200418 }
419
420 return $data;
421 }
422
423 // --------------------------------------------------------------------
424
425 /**
426 * Encrypt via OpenSSL
427 *
428 * @param string $data Input data
429 * @param array $params Input parameters
430 * @return string
431 */
432 protected function _openssl_encrypt($data, $params)
433 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200434 if (empty($params['handle']))
435 {
436 return FALSE;
437 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300438
439 $iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
440 ? openssl_random_pseudo_bytes($iv_size)
441 : NULL;
Andrey Andreev29cade42014-02-04 14:05:58 +0200442
Andrey Andreev818aedb2014-02-03 11:30:25 +0200443 $data = openssl_encrypt(
444 $data,
445 $params['handle'],
446 $params['key'],
447 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300448 $iv
Andrey Andreev818aedb2014-02-03 11:30:25 +0200449 );
450
451 if ($data === FALSE)
452 {
453 return FALSE;
454 }
455
Andrey Andreev1e83d692014-06-19 20:08:59 +0300456 return $iv.$data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200457 }
458
459 // --------------------------------------------------------------------
460
461 /**
462 * Decrypt
463 *
464 * @param string $data Encrypted data
465 * @param array $params Input parameters
466 * @return string
467 */
468 public function decrypt($data, array $params = NULL)
469 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200470 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200471 {
472 return FALSE;
473 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200474
475 if (isset($params['hmac_digest']))
476 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200477 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200478 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200479 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200480 ? $this->_digests[$params['hmac_digest']] * 2
481 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200482
483 if (strlen($data) <= $digest_size)
484 {
485 return FALSE;
486 }
487
488 $hmac_input = substr($data, 0, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200489 $data = substr($data, $digest_size);
490
Andrey Andreev7c554482014-02-06 02:38:27 +0200491 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
492 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
493
494 // Time-attack-safe comparison
495 $diff = 0;
496 for ($i = 0; $i < $digest_size; $i++)
497 {
498 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
499 }
500
501 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200502 {
503 return FALSE;
504 }
505 }
506
507 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200508 {
509 $data = base64_decode($data);
510 }
511
Andrey Andreev81e10642014-02-07 01:43:36 +0200512 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
513
Andrey Andreev818aedb2014-02-03 11:30:25 +0200514 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
515 }
516
517 // --------------------------------------------------------------------
518
519 /**
520 * Decrypt via MCrypt
521 *
522 * @param string $data Encrypted data
523 * @param array $params Input parameters
524 * @return string
525 */
526 protected function _mcrypt_decrypt($data, $params)
527 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200528 if ( ! is_resource($params['handle']))
529 {
530 return FALSE;
531 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300532
533 // The greater-than-1 comparison is mostly a work-around for a bug,
534 // where 1 is returned for ARCFour instead of 0.
535 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200536 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300537 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreeve7516b02014-02-05 13:45:31 +0200538 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300539 $iv = substr($data, 0, $iv_size);
540 $data = substr($data, $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200541 }
542 else
543 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300544 // MCrypt is dumb and this is ignored, only size matters
545 $iv = str_repeat("\x0", $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200546 }
547 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300548 else
549 {
550 $iv = NULL;
551 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200552
Andrey Andreev1e83d692014-06-19 20:08:59 +0300553 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200554 {
555 if ($params['handle'] !== $this->_handle)
556 {
557 mcrypt_module_close($params['handle']);
558 }
559
560 return FALSE;
561 }
562
563 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200564 // Remove PKCS#7 padding, if necessary
565 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
566 {
567 $data = substr($data, 0, -ord($data[strlen($data)-1]));
568 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200569
570 mcrypt_generic_deinit($params['handle']);
571 if ($params['handle'] !== $this->_handle)
572 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200573 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200574 }
575
Andrey Andreevf4017672014-02-05 18:51:15 +0200576 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200577 }
578
579 // --------------------------------------------------------------------
580
581 /**
582 * Decrypt via OpenSSL
583 *
584 * @param string $data Encrypted data
585 * @param array $params Input parameters
586 * @return string
587 */
588 protected function _openssl_decrypt($data, $params)
589 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300590 if ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreeve7516b02014-02-05 13:45:31 +0200591 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300592 $iv = substr($data, 0, $iv_size);
593 $data = substr($data, $iv_size);
594 }
595 else
596 {
597 $iv = NULL;
Andrey Andreeve7516b02014-02-05 13:45:31 +0200598 }
599
Andrey Andreev29cade42014-02-04 14:05:58 +0200600 return empty($params['handle'])
601 ? FALSE
602 : openssl_decrypt(
603 $data,
604 $params['handle'],
605 $params['key'],
606 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300607 $iv
Andrey Andreev29cade42014-02-04 14:05:58 +0200608 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200609 }
610
611 // --------------------------------------------------------------------
612
613 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200614 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200615 *
616 * @param array $params Input parameters
617 * @return array
618 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200619 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200620 {
621 if (empty($params))
622 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200623 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200624 ? array(
625 'handle' => $this->_handle,
626 'cipher' => $this->_cipher,
627 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200628 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200629 'base64' => TRUE,
Andrey Andreevab9971f2014-07-02 19:09:08 +0300630 'hmac_digest' => 'sha512',
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200631 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200632 )
633 : FALSE;
634 }
635 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
636 {
637 return FALSE;
638 }
639
Andrey Andreevf4017672014-02-05 18:51:15 +0200640 if (isset($params['mode']))
641 {
642 $params['mode'] = strtolower($params['mode']);
643 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
644 {
645 return FALSE;
646 }
647 else
648 {
649 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
650 }
651 }
652
Andrey Andreevab9971f2014-07-02 19:09:08 +0300653 if (isset($params['hmac']) && $params['hmac'] === FALSE)
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200654 {
655 $params['hmac_digest'] = $params['hmac_key'] = NULL;
656 }
657 else
658 {
659 if ( ! isset($params['hmac_key']))
660 {
661 return FALSE;
662 }
663 elseif (isset($params['hmac_digest']))
664 {
665 $params['hmac_digest'] = strtolower($params['hmac_digest']);
666 if ( ! isset($this->_digests[$params['hmac_digest']]))
667 {
668 return FALSE;
669 }
670 }
671 else
672 {
673 $params['hmac_digest'] = 'sha512';
674 }
675 }
676
Andrey Andreev818aedb2014-02-03 11:30:25 +0200677 $params = array(
678 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200679 'cipher' => $params['cipher'],
680 'mode' => $params['mode'],
681 'key' => $params['key'],
Andrey Andreev4b450652014-02-10 06:59:54 +0200682 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200683 'hmac_digest' => $params['hmac_digest'],
684 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200685 );
686
Andrey Andreev818aedb2014-02-03 11:30:25 +0200687 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200688 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
689 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
690 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200691
692 return $params;
693 }
694
695 // --------------------------------------------------------------------
696
697 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200698 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200699 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200700 * @param string $cipher Cipher name
701 * @param string $mode Encryption mode
702 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200703 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200704 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200705 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200706 return mcrypt_module_open($cipher, '', $mode, '');
707 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200708
Andrey Andreev29cade42014-02-04 14:05:58 +0200709 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200710
Andrey Andreev29cade42014-02-04 14:05:58 +0200711 /**
712 * Get OpenSSL handle
713 *
714 * @param string $cipher Cipher name
715 * @param string $mode Encryption mode
716 * @return string
717 */
718 protected function _openssl_get_handle($cipher, $mode)
719 {
720 // OpenSSL methods aren't suffixed with '-stream' for this mode
721 return ($mode === 'stream')
722 ? $cipher
723 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200724 }
725
726 // --------------------------------------------------------------------
727
728 /**
729 * Cipher alias
730 *
731 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
732 *
733 * @param string $cipher Cipher name
734 * @return void
735 */
736 protected function _cipher_alias(&$cipher)
737 {
738 static $dictionary;
739
740 if (empty($dictionary))
741 {
742 $dictionary = array(
743 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200744 'aes-128' => 'rijndael-128',
745 'aes-192' => 'rijndael-128',
746 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200747 'des3-ede3' => 'tripledes',
748 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200749 'cast5' => 'cast-128',
750 'rc4' => 'arcfour',
751 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200752 ),
753 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200754 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200755 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200756 'blowfish' => 'bf',
757 'cast-128' => 'cast5',
758 'arcfour' => 'rc4-40',
759 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200760 )
761 );
762
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200763 // Notes:
764 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200765 // - Rijndael-128 is, at the same time all three of AES-128,
766 // AES-192 and AES-256. The only difference between them is
767 // the key size. Rijndael-192, Rijndael-256 on the other hand
768 // also have different block sizes and are NOT AES-compatible.
769 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200770 // - Blowfish is said to be supporting key sizes between
771 // 4 and 56 bytes, but it appears that between MCrypt and
772 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200773 // Also, don't know what MCrypt's 'blowfish-compat' is.
774 //
775 // - CAST-128/CAST5 produces a longer cipher when encrypted via
776 // OpenSSL, but (strangely enough) can be decrypted by either
777 // extension anyway.
Andrey Andreev18767e32014-03-04 22:21:35 +0200778 // Also, it appears that OpenSSL uses 16 rounds regardless of
779 // the key size, while RFC2144 says that for key sizes lower
780 // than 11 bytes, only 12 rounds should be used. This makes
781 // it portable only with keys of between 11 and 16 bytes.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200782 //
783 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
784 // Its 'rc4-40' cipher method seems to work flawlessly, yet
785 // there's another one, 'rc4' that only works with a 16-byte key.
786 //
787 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200788 //
789 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200790 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200791 // - RC2 is NOT compatible and only an obscure forum post
792 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200793 }
794
Andrey Andreev50ccc382014-02-04 23:30:06 +0200795 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200796 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200797 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200798 }
799 }
800
801 // --------------------------------------------------------------------
802
803 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200804 * HKDF
805 *
806 * @link https://tools.ietf.org/rfc/rfc5869.txt
807 * @param $key Input key
808 * @param $digest A SHA-2 hashing algorithm
809 * @param $salt Optional salt
Andrey Andreev29cade42014-02-04 14:05:58 +0200810 * @param $length Output length (defaults to the selected digest size)
Andrey Andreev4b450652014-02-10 06:59:54 +0200811 * @param $info Optional context/application-specific info
Andrey Andreev29cade42014-02-04 14:05:58 +0200812 * @return string A pseudo-random key
813 */
814 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
815 {
816 if ( ! isset($this->_digests[$digest]))
817 {
818 return FALSE;
819 }
820
821 if (empty($length) OR ! is_int($length))
822 {
823 $length = $this->_digests[$digest];
824 }
825 elseif ($length > (255 * $this->_digests[$digest]))
826 {
827 return FALSE;
828 }
829
Andrey Andreeva4f113e2014-02-18 21:18:31 +0200830 strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
Andrey Andreev29cade42014-02-04 14:05:58 +0200831
832 $prk = hash_hmac($digest, $key, $salt, TRUE);
833 $key = '';
834 for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
835 {
836 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
837 $key .= $key_block;
838 }
839
840 return substr($key, 0, $length);
841 }
842
843 // --------------------------------------------------------------------
844
845 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200846 * __get() magic
847 *
848 * @param string $key Property name
849 * @return mixed
850 */
851 public function __get($key)
852 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200853 // Because aliases
854 if ($key === 'mode')
855 {
856 return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);
857 }
858 elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE))
859 {
860 return $this->{'_'.$key};
861 }
862
863 return NULL;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200864 }
865
866}
867
868/* End of file Encryption.php */
869/* Location: ./system/libraries/Encryption.php */