blob: 583ddac3b44203e4f208b4de1c8e1c9396814bcb [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 {
Andrey Andreeve8088d62014-02-06 05:01:48 +0200370 // The greater-than-1 comparison is mostly a work-around for a bug,
371 // where 1 is returned for ARCFour instead of 0.
372 $params['iv'] = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreev8e202162014-02-05 18:59:55 +0200373 ? mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200374 : NULL;
375 }
376
Andrey Andreeve8088d62014-02-06 05:01:48 +0200377 // CAST-128 compatibility (http://tools.ietf.org/rfc/rfc2144.txt)
378 //
379 // RFC2144 says that keys shorter than 16 bytes are to be padded with
380 // zero bytes to 16 bytes, but (surprise) MCrypt doesn't do that.
381 if ($params['cipher'] === 'cast-128' && ($kl = strlen($params['key'])) < 16)
382 {
383 $params['key'] .= str_repeat("\x0", 16 - $kl);
384 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200385
386 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200387 {
388 if ($params['handle'] !== $this->_handle)
389 {
390 mcrypt_module_close($params['handle']);
391 }
392
393 return FALSE;
394 }
395
396 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200397 // and other implementations outside of PHP.
398 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
399 {
400 $block_size = mcrypt_enc_get_block_size($params['handle']);
401 $pad = $block_size - (strlen($data) % $block_size);
402 $data .= str_repeat(chr($pad), $pad);
403 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200404
Andrey Andreeve7516b02014-02-05 13:45:31 +0200405 // Work-around for yet another strange behavior in MCrypt.
406 //
407 // When encrypting in ECB mode, the IV is ignored. Yet
408 // mcrypt_enc_get_iv_size() returns a value larger than 0
409 // even if ECB is used AND mcrypt_generic_init() complains
410 // if you don't pass an IV with length equal to the said
411 // return value.
412 //
413 // This probably would've been fine (even though still wasteful),
414 // but OpenSSL isn't that dumb and we need to make the process
415 // portable, so ...
416 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
417 ? $params['iv'].mcrypt_generic($params['handle'], $data)
418 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200419
420 mcrypt_generic_deinit($params['handle']);
421 if ($params['handle'] !== $this->_handle)
422 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200423 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200424 }
425
426 return $data;
427 }
428
429 // --------------------------------------------------------------------
430
431 /**
432 * Encrypt via OpenSSL
433 *
434 * @param string $data Input data
435 * @param array $params Input parameters
436 * @return string
437 */
438 protected function _openssl_encrypt($data, $params)
439 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200440 if (empty($params['handle']))
441 {
442 return FALSE;
443 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200444 elseif ( ! isset($params['iv']))
445 {
446 $params['iv'] = ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreev8e202162014-02-05 18:59:55 +0200447 ? openssl_random_pseudo_bytes($iv_size)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200448 : NULL;
449 }
Andrey Andreev29cade42014-02-04 14:05:58 +0200450
Andrey Andreev818aedb2014-02-03 11:30:25 +0200451 $data = openssl_encrypt(
452 $data,
453 $params['handle'],
454 $params['key'],
455 1, // DO NOT TOUCH!
456 $params['iv']
457 );
458
459 if ($data === FALSE)
460 {
461 return FALSE;
462 }
463
464 return $params['iv'].$data;
465 }
466
467 // --------------------------------------------------------------------
468
469 /**
470 * Decrypt
471 *
472 * @param string $data Encrypted data
473 * @param array $params Input parameters
474 * @return string
475 */
476 public function decrypt($data, array $params = NULL)
477 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200478 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200479 {
480 return FALSE;
481 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200482 elseif ( ! isset($params['key']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200483 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200484 if ( ! isset($this->_key))
Andrey Andreev29cade42014-02-04 14:05:58 +0200485 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200486 return show_error('Encryption: You are required to set an encryption key in your configuration.');
Andrey Andreev29cade42014-02-04 14:05:58 +0200487 }
488
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200489 $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, strlen($this->_key), 'encryption');
490 }
491
492 if (isset($params['hmac_digest']))
493 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200494 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200495 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200496 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200497 ? $this->_digests[$params['hmac_digest']] * 2
498 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200499
500 if (strlen($data) <= $digest_size)
501 {
502 return FALSE;
503 }
504
505 $hmac_input = substr($data, 0, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200506 $data = substr($data, $digest_size);
507
Andrey Andreev7c554482014-02-06 02:38:27 +0200508 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
509 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
510
511 // Time-attack-safe comparison
512 $diff = 0;
513 for ($i = 0; $i < $digest_size; $i++)
514 {
515 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
516 }
517
518 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200519 {
520 return FALSE;
521 }
522 }
523
524 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200525 {
526 $data = base64_decode($data);
527 }
528
Andrey Andreeve7516b02014-02-05 13:45:31 +0200529 if (isset($params['iv']) && strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200530 {
531 $data = substr($data, $iv_size);
532 }
533
534 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
535 }
536
537 // --------------------------------------------------------------------
538
539 /**
540 * Decrypt via MCrypt
541 *
542 * @param string $data Encrypted data
543 * @param array $params Input parameters
544 * @return string
545 */
546 protected function _mcrypt_decrypt($data, $params)
547 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200548 if ( ! is_resource($params['handle']))
549 {
550 return FALSE;
551 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200552 elseif ( ! isset($params['iv']))
553 {
Andrey Andreeve8088d62014-02-06 05:01:48 +0200554 // The greater-than-1 comparison is mostly a work-around for a bug,
555 // where 1 is returned for ARCFour instead of 0.
556 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200557 {
558 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
559 {
560 $params['iv'] = substr($data, 0, $iv_size);
561 $data = substr($data, $iv_size);
562 }
563 else
564 {
565 // MCrypt is dumb and this is ignored, only size matters
566 $params['iv'] = str_repeat("\x0", $iv_size);
567 }
568 }
569 else
570 {
571 $params['iv'] = NULL;
572 }
573 }
574
Andrey Andreeve8088d62014-02-06 05:01:48 +0200575 // CAST-128 compatibility (http://tools.ietf.org/rfc/rfc2144.txt)
576 //
577 // RFC2144 says that keys shorter than 16 bytes are to be padded with
578 // zero bytes to 16 bytes, but (surprise) MCrypt doesn't do that.
579 if ($params['cipher'] === 'cast-128' && ($kl = strlen($params['key'])) < 16)
580 {
581 $params['key'] .= str_repeat("\x0", 16 - $kl);
582 }
583
Andrey Andreeve7516b02014-02-05 13:45:31 +0200584 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200585 {
586 if ($params['handle'] !== $this->_handle)
587 {
588 mcrypt_module_close($params['handle']);
589 }
590
591 return FALSE;
592 }
593
594 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200595 // Remove PKCS#7 padding, if necessary
596 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
597 {
598 $data = substr($data, 0, -ord($data[strlen($data)-1]));
599 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200600
601 mcrypt_generic_deinit($params['handle']);
602 if ($params['handle'] !== $this->_handle)
603 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200604 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200605 }
606
Andrey Andreevf4017672014-02-05 18:51:15 +0200607 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200608 }
609
610 // --------------------------------------------------------------------
611
612 /**
613 * Decrypt via OpenSSL
614 *
615 * @param string $data Encrypted data
616 * @param array $params Input parameters
617 * @return string
618 */
619 protected function _openssl_decrypt($data, $params)
620 {
Andrey Andreeve7516b02014-02-05 13:45:31 +0200621 if ( ! isset($params['iv']))
622 {
623 if ($iv_size = openssl_cipher_iv_length($params['handle']))
624 {
625 $params['iv'] = substr($data, 0, $iv_size);
626 $data = substr($data, $iv_size);
627 }
628 else
629 {
630 $params['iv'] = NULL;
631 }
632 }
633
Andrey Andreev29cade42014-02-04 14:05:58 +0200634 return empty($params['handle'])
635 ? FALSE
636 : openssl_decrypt(
637 $data,
638 $params['handle'],
639 $params['key'],
640 1, // DO NOT TOUCH!
641 $params['iv']
642 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200643 }
644
645 // --------------------------------------------------------------------
646
647 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200648 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200649 *
650 * @param array $params Input parameters
651 * @return array
652 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200653 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200654 {
655 if (empty($params))
656 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200657 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200658 ? array(
659 'handle' => $this->_handle,
660 'cipher' => $this->_cipher,
661 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200662 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200663 'base64' => TRUE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200664 'hmac_digest' => ($this->_mode !== 'gcm' ? 'sha512' : NULL),
665 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200666 )
667 : FALSE;
668 }
669 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
670 {
671 return FALSE;
672 }
673
Andrey Andreevf4017672014-02-05 18:51:15 +0200674 if (isset($params['mode']))
675 {
676 $params['mode'] = strtolower($params['mode']);
677 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
678 {
679 return FALSE;
680 }
681 else
682 {
683 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
684 }
685 }
686
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200687 if ($params['mode'] === 'gcm' OR isset($params['hmac']) && $params['hmac'] === FALSE)
688 {
689 $params['hmac_digest'] = $params['hmac_key'] = NULL;
690 }
691 else
692 {
693 if ( ! isset($params['hmac_key']))
694 {
695 return FALSE;
696 }
697 elseif (isset($params['hmac_digest']))
698 {
699 $params['hmac_digest'] = strtolower($params['hmac_digest']);
700 if ( ! isset($this->_digests[$params['hmac_digest']]))
701 {
702 return FALSE;
703 }
704 }
705 else
706 {
707 $params['hmac_digest'] = 'sha512';
708 }
709 }
710
Andrey Andreev818aedb2014-02-03 11:30:25 +0200711 $params = array(
712 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200713 'cipher' => $params['cipher'],
714 'mode' => $params['mode'],
715 'key' => $params['key'],
Andrey Andreev818aedb2014-02-03 11:30:25 +0200716 'iv' => isset($params['iv']) ? $params['iv'] : NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200717 'base64' => isset($params['base64']) ? $params['base64'] : TRUE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200718 'hmac_digest' => $params['hmac_digest'],
719 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200720 );
721
Andrey Andreev818aedb2014-02-03 11:30:25 +0200722 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200723 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
724 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
725 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200726
727 return $params;
728 }
729
730 // --------------------------------------------------------------------
731
732 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200733 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200734 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200735 * @param string $cipher Cipher name
736 * @param string $mode Encryption mode
737 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200738 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200739 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200740 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200741 return mcrypt_module_open($cipher, '', $mode, '');
742 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200743
Andrey Andreev29cade42014-02-04 14:05:58 +0200744 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200745
Andrey Andreev29cade42014-02-04 14:05:58 +0200746 /**
747 * Get OpenSSL handle
748 *
749 * @param string $cipher Cipher name
750 * @param string $mode Encryption mode
751 * @return string
752 */
753 protected function _openssl_get_handle($cipher, $mode)
754 {
755 // OpenSSL methods aren't suffixed with '-stream' for this mode
756 return ($mode === 'stream')
757 ? $cipher
758 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200759 }
760
761 // --------------------------------------------------------------------
762
763 /**
764 * Cipher alias
765 *
766 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
767 *
768 * @param string $cipher Cipher name
769 * @return void
770 */
771 protected function _cipher_alias(&$cipher)
772 {
773 static $dictionary;
774
775 if (empty($dictionary))
776 {
777 $dictionary = array(
778 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200779 'aes-128' => 'rijndael-128',
780 'aes-192' => 'rijndael-128',
781 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200782 'des3-ede3' => 'tripledes',
783 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200784 'cast5' => 'cast-128',
785 'rc4' => 'arcfour',
786 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200787 ),
788 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200789 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200790 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200791 'blowfish' => 'bf',
792 'cast-128' => 'cast5',
793 'arcfour' => 'rc4-40',
794 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200795 )
796 );
797
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200798 // Notes:
799 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200800 // - Rijndael-128 is, at the same time all three of AES-128,
801 // AES-192 and AES-256. The only difference between them is
802 // the key size. Rijndael-192, Rijndael-256 on the other hand
803 // also have different block sizes and are NOT AES-compatible.
804 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200805 // - Blowfish is said to be supporting key sizes between
806 // 4 and 56 bytes, but it appears that between MCrypt and
807 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200808 // Also, don't know what MCrypt's 'blowfish-compat' is.
809 //
810 // - CAST-128/CAST5 produces a longer cipher when encrypted via
811 // OpenSSL, but (strangely enough) can be decrypted by either
812 // extension anyway.
813 // Also, RFC2144 says that the cipher supports key sizes
814 // between 5 and 16 bytes by the implementation actually
815 // zero-padding them to 16 bytes, but MCrypt doesn't do that.
816 //
817 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
818 // Its 'rc4-40' cipher method seems to work flawlessly, yet
819 // there's another one, 'rc4' that only works with a 16-byte key.
820 //
821 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200822 //
823 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200824 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200825 // - RC2 is NOT compatible and only an obscure forum post
826 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200827 }
828
Andrey Andreev50ccc382014-02-04 23:30:06 +0200829 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200830 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200831 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200832 }
833 }
834
835 // --------------------------------------------------------------------
836
837 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200838 * HKDF
839 *
840 * @link https://tools.ietf.org/rfc/rfc5869.txt
841 * @param $key Input key
842 * @param $digest A SHA-2 hashing algorithm
843 * @param $salt Optional salt
844 * @param $info Optional context/application-specific info
845 * @param $length Output length (defaults to the selected digest size)
846 * @return string A pseudo-random key
847 */
848 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
849 {
850 if ( ! isset($this->_digests[$digest]))
851 {
852 return FALSE;
853 }
854
855 if (empty($length) OR ! is_int($length))
856 {
857 $length = $this->_digests[$digest];
858 }
859 elseif ($length > (255 * $this->_digests[$digest]))
860 {
861 return FALSE;
862 }
863
864 isset($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
865
866 $prk = hash_hmac($digest, $key, $salt, TRUE);
867 $key = '';
868 for ($key_block = '', $block_index = 1; strlen($key) < $length; $block_index++)
869 {
870 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
871 $key .= $key_block;
872 }
873
874 return substr($key, 0, $length);
875 }
876
877 // --------------------------------------------------------------------
878
879 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200880 * __get() magic
881 *
882 * @param string $key Property name
883 * @return mixed
884 */
885 public function __get($key)
886 {
Andrey Andreev912831f2014-02-04 17:21:37 +0200887 return in_array($key, array('cipher', 'mode', 'driver', 'drivers', 'digests'), TRUE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200888 ? $this->{'_'.$key}
889 : NULL;
890 }
891
892}
893
894/* End of file Encryption.php */
895/* Location: ./system/libraries/Encryption.php */