blob: a10a5c20ccc51a564b27ce986459596fceeb8de3 [file] [log] [blame]
Andrey Andreev818aedb2014-02-03 11:30:25 +02001<?php
2/**
3 * CodeIgniter
4 *
Andrey Andreevfe9309d2015-01-09 17:48:58 +02005 * An open source application development framework for PHP
Andrey Andreev818aedb2014-02-03 11:30:25 +02006 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +02007 * This content is released under the MIT License (MIT)
Andrey Andreev818aedb2014-02-03 11:30:25 +02008 *
Andrey Andreev125ef472016-01-11 12:33:00 +02009 * Copyright (c) 2014 - 2016, British Columbia Institute of Technology
Andrey Andreev818aedb2014-02-03 11:30:25 +020010 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020011 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
Andrey Andreev818aedb2014-02-03 11:30:25 +020017 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020018 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 * @package CodeIgniter
30 * @author EllisLab Dev Team
Andrey Andreev1924e872016-01-11 12:55:34 +020031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
Andrey Andreev125ef472016-01-11 12:33:00 +020032 * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/)
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020033 * @license http://opensource.org/licenses/MIT MIT License
Andrey Andreevbd202c92016-01-11 12:50:18 +020034 * @link https://codeigniter.com
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020035 * @since Version 3.0.0
Andrey Andreev818aedb2014-02-03 11:30:25 +020036 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * CodeIgniter Encryption Class
42 *
43 * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions.
44 *
45 * @package CodeIgniter
46 * @subpackage Libraries
47 * @category Libraries
48 * @author Andrey Andreev
Andrey Andreevbd202c92016-01-11 12:50:18 +020049 * @link https://codeigniter.com/user_guide/libraries/encryption.html
Andrey Andreev818aedb2014-02-03 11:30:25 +020050 */
51class CI_Encryption {
52
53 /**
54 * Encryption cipher
55 *
56 * @var string
57 */
Andrey Andreev81e10642014-02-07 01:43:36 +020058 protected $_cipher = 'aes-128';
Andrey Andreev818aedb2014-02-03 11:30:25 +020059
60 /**
61 * Cipher mode
62 *
63 * @var string
64 */
65 protected $_mode = 'cbc';
66
67 /**
68 * Cipher handle
69 *
70 * @var mixed
71 */
72 protected $_handle;
73
74 /**
75 * Encryption key
76 *
77 * @var string
78 */
79 protected $_key;
80
81 /**
82 * PHP extension to be used
83 *
84 * @var string
85 */
86 protected $_driver;
87
88 /**
89 * List of usable drivers (PHP extensions)
90 *
91 * @var array
92 */
93 protected $_drivers = array();
94
Andrey Andreev29cade42014-02-04 14:05:58 +020095 /**
Andrey Andreevf4017672014-02-05 18:51:15 +020096 * List of available modes
97 *
98 * @var array
99 */
100 protected $_modes = array(
101 'mcrypt' => array(
102 'cbc' => 'cbc',
103 'ecb' => 'ecb',
104 'ofb' => 'nofb',
105 'ofb8' => 'ofb',
106 'cfb' => 'ncfb',
107 'cfb8' => 'cfb',
108 'ctr' => 'ctr',
109 'stream' => 'stream'
110 ),
111 'openssl' => array(
112 'cbc' => 'cbc',
113 'ecb' => 'ecb',
114 'ofb' => 'ofb',
115 'cfb' => 'cfb',
116 'cfb8' => 'cfb8',
117 'ctr' => 'ctr',
118 'stream' => '',
Andrey Andreevf4017672014-02-05 18:51:15 +0200119 'xts' => 'xts'
120 )
121 );
122
123 /**
Achraf Almouloudida7a2202015-04-01 05:27:56 +0100124 * List of supported HMAC algorithms
Andrey Andreev29cade42014-02-04 14:05:58 +0200125 *
126 * name => digest size pairs
127 *
128 * @var array
129 */
130 protected $_digests = array(
131 'sha224' => 28,
132 'sha256' => 32,
133 'sha384' => 48,
134 'sha512' => 64
135 );
136
Andrey Andreev2da35502014-07-07 14:41:57 +0300137 /**
138 * mbstring.func_override flag
139 *
140 * @var bool
141 */
142 protected static $func_override;
143
Andrey Andreev818aedb2014-02-03 11:30:25 +0200144 // --------------------------------------------------------------------
145
146 /**
147 * Class constructor
148 *
149 * @param array $params Configuration parameters
150 * @return void
151 */
152 public function __construct(array $params = array())
153 {
154 $this->_drivers = array(
Andrey Andreev8e202162014-02-05 18:59:55 +0200155 'mcrypt' => defined('MCRYPT_DEV_URANDOM'),
Andrey Andreev818aedb2014-02-03 11:30:25 +0200156 // While OpenSSL is available for PHP 5.3.0, an IV parameter
157 // for the encrypt/decrypt functions is only available since 5.3.3
158 'openssl' => (is_php('5.3.3') && extension_loaded('openssl'))
159 );
160
161 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
162 {
Gabriel Potkány1fb50002015-02-04 01:45:59 +0100163 show_error('Encryption: Unable to find an available encryption driver.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200164 }
165
Andrey Andreev2da35502014-07-07 14:41:57 +0300166 isset(self::$func_override) OR self::$func_override = (extension_loaded('mbstring') && ini_get('mbstring.func_override'));
Andrey Andreev818aedb2014-02-03 11:30:25 +0200167 $this->initialize($params);
Andrey Andreev2da35502014-07-07 14:41:57 +0300168
169 if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200170 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200171 $this->_key = $key;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200172 }
173
Andrey Andreev90726b82015-01-20 12:39:22 +0200174 log_message('info', 'Encryption Class Initialized');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200175 }
176
177 // --------------------------------------------------------------------
178
179 /**
180 * Initialize
181 *
182 * @param array $params Configuration parameters
183 * @return CI_Encryption
184 */
185 public function initialize(array $params)
186 {
187 if ( ! empty($params['driver']))
188 {
189 if (isset($this->_drivers[$params['driver']]))
190 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200191 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200192 {
193 $this->_driver = $params['driver'];
194 }
195 else
196 {
197 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
198 }
199 }
200 else
201 {
202 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
203 }
204 }
205
Andrey Andreev912831f2014-02-04 17:21:37 +0200206 if (empty($this->_driver))
207 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200208 $this->_driver = ($this->_drivers['openssl'] === TRUE)
209 ? 'openssl'
210 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200211
212 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
213 }
214
Andrey Andreev50c9ea12014-11-07 16:57:41 +0200215 empty($params['cipher']) && $params['cipher'] = $this->_cipher;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200216 empty($params['key']) OR $this->_key = $params['key'];
217 $this->{'_'.$this->_driver.'_initialize'}($params);
218 return $this;
219 }
220
221 // --------------------------------------------------------------------
222
223 /**
224 * Initialize MCrypt
225 *
226 * @param array $params Configuration parameters
227 * @return void
228 */
229 protected function _mcrypt_initialize($params)
230 {
231 if ( ! empty($params['cipher']))
232 {
233 $params['cipher'] = strtolower($params['cipher']);
234 $this->_cipher_alias($params['cipher']);
235
236 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
237 {
238 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
239 }
240 else
241 {
242 $this->_cipher = $params['cipher'];
243 }
244 }
245
246 if ( ! empty($params['mode']))
247 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200248 $params['mode'] = strtolower($params['mode']);
249 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200250 {
Gabriel Potkány8194ce52015-02-04 10:34:24 +0100251 log_message('error', 'Encryption: MCrypt mode '.strtoupper($params['mode']).' is not available.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200252 }
253 else
254 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200255 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200256 }
257 }
258
259 if (isset($this->_cipher, $this->_mode))
260 {
261 if (is_resource($this->_handle)
262 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
263 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
264 )
265 {
266 mcrypt_module_close($this->_handle);
267 }
268
269 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
270 {
Andrey Andreev90726b82015-01-20 12:39:22 +0200271 log_message('info', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200272 }
273 else
274 {
275 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
276 }
277 }
278 }
279
280 // --------------------------------------------------------------------
281
282 /**
283 * Initialize OpenSSL
284 *
285 * @param array $params Configuration parameters
286 * @return void
287 */
288 protected function _openssl_initialize($params)
289 {
290 if ( ! empty($params['cipher']))
291 {
292 $params['cipher'] = strtolower($params['cipher']);
293 $this->_cipher_alias($params['cipher']);
294 $this->_cipher = $params['cipher'];
295 }
296
297 if ( ! empty($params['mode']))
298 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200299 $params['mode'] = strtolower($params['mode']);
300 if ( ! isset($this->_modes['openssl'][$params['mode']]))
301 {
Gabriel Potkány8194ce52015-02-04 10:34:24 +0100302 log_message('error', 'Encryption: OpenSSL mode '.strtoupper($params['mode']).' is not available.');
Andrey Andreevf4017672014-02-05 18:51:15 +0200303 }
304 else
305 {
306 $this->_mode = $this->_modes['openssl'][$params['mode']];
307 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200308 }
309
310 if (isset($this->_cipher, $this->_mode))
311 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200312 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
313 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200314 ? $this->_cipher
315 : $this->_cipher.'-'.$this->_mode;
316
Andrey Andreev50ccc382014-02-04 23:30:06 +0200317 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200318 {
319 $this->_handle = NULL;
320 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
321 }
322 else
323 {
324 $this->_handle = $handle;
Andrey Andreev90726b82015-01-20 12:39:22 +0200325 log_message('info', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200326 }
327 }
328 }
329
330 // --------------------------------------------------------------------
331
332 /**
Andrey Andreev42183de2014-06-22 00:09:36 +0300333 * Create a random key
334 *
335 * @param int $length Output length
336 * @return string
337 */
338 public function create_key($length)
339 {
Andrey Andreev5afa3482015-11-24 11:48:39 +0200340 if (function_exists('random_bytes'))
341 {
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200342 try
343 {
344 return random_bytes((int) $length);
345 }
346 catch (Exception $e)
347 {
348 log_message('error', $e->getMessage());
349 return FALSE;
350 }
351 }
352 elseif (defined('MCRYPT_DEV_URANDOM'))
353 {
354 return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
Andrey Andreev5afa3482015-11-24 11:48:39 +0200355 }
356
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200357 $is_secure = NULL;
358 $key = openssl_random_pseudo_bytes($length, $is_secure);
359 return ($is_secure === TRUE)
360 ? $key
361 : FALSE;
Andrey Andreev42183de2014-06-22 00:09:36 +0300362 }
363
364 // --------------------------------------------------------------------
365
366 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200367 * Encrypt
368 *
369 * @param string $data Input data
370 * @param array $params Input parameters
371 * @return string
372 */
373 public function encrypt($data, array $params = NULL)
374 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200375 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200376 {
377 return FALSE;
378 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200379
Andrey Andreev2da35502014-07-07 14:41:57 +0300380 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200381
382 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
383 {
384 return FALSE;
385 }
386
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200387 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200388
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200389 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200390 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200391 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
392 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200393 }
394
395 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200396 }
397
398 // --------------------------------------------------------------------
399
400 /**
401 * Encrypt via MCrypt
402 *
403 * @param string $data Input data
404 * @param array $params Input parameters
405 * @return string
406 */
407 protected function _mcrypt_encrypt($data, $params)
408 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200409 if ( ! is_resource($params['handle']))
410 {
411 return FALSE;
412 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200413
Andrey Andreev1e83d692014-06-19 20:08:59 +0300414 // The greater-than-1 comparison is mostly a work-around for a bug,
415 // where 1 is returned for ARCFour instead of 0.
416 $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200417 ? $this->create_key($iv_size)
Andrey Andreev1e83d692014-06-19 20:08:59 +0300418 : NULL;
419
420 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200421 {
422 if ($params['handle'] !== $this->_handle)
423 {
424 mcrypt_module_close($params['handle']);
425 }
426
427 return FALSE;
428 }
429
430 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200431 // and other implementations outside of PHP.
432 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
433 {
434 $block_size = mcrypt_enc_get_block_size($params['handle']);
Andrey Andreev2da35502014-07-07 14:41:57 +0300435 $pad = $block_size - (self::strlen($data) % $block_size);
Andrey Andreevf4017672014-02-05 18:51:15 +0200436 $data .= str_repeat(chr($pad), $pad);
437 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200438
Andrey Andreeve7516b02014-02-05 13:45:31 +0200439 // Work-around for yet another strange behavior in MCrypt.
440 //
441 // When encrypting in ECB mode, the IV is ignored. Yet
442 // mcrypt_enc_get_iv_size() returns a value larger than 0
443 // even if ECB is used AND mcrypt_generic_init() complains
444 // if you don't pass an IV with length equal to the said
445 // return value.
446 //
447 // This probably would've been fine (even though still wasteful),
448 // but OpenSSL isn't that dumb and we need to make the process
449 // portable, so ...
450 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreev1e83d692014-06-19 20:08:59 +0300451 ? $iv.mcrypt_generic($params['handle'], $data)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200452 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200453
454 mcrypt_generic_deinit($params['handle']);
455 if ($params['handle'] !== $this->_handle)
456 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200457 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200458 }
459
460 return $data;
461 }
462
463 // --------------------------------------------------------------------
464
465 /**
466 * Encrypt via OpenSSL
467 *
468 * @param string $data Input data
469 * @param array $params Input parameters
470 * @return string
471 */
472 protected function _openssl_encrypt($data, $params)
473 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200474 if (empty($params['handle']))
475 {
476 return FALSE;
477 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300478
479 $iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200480 ? $this->create_key($iv_size)
Andrey Andreev1e83d692014-06-19 20:08:59 +0300481 : NULL;
Andrey Andreev29cade42014-02-04 14:05:58 +0200482
Andrey Andreev818aedb2014-02-03 11:30:25 +0200483 $data = openssl_encrypt(
484 $data,
485 $params['handle'],
486 $params['key'],
487 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300488 $iv
Andrey Andreev818aedb2014-02-03 11:30:25 +0200489 );
490
491 if ($data === FALSE)
492 {
493 return FALSE;
494 }
495
Andrey Andreev1e83d692014-06-19 20:08:59 +0300496 return $iv.$data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200497 }
498
499 // --------------------------------------------------------------------
500
501 /**
502 * Decrypt
503 *
504 * @param string $data Encrypted data
505 * @param array $params Input parameters
506 * @return string
507 */
508 public function decrypt($data, array $params = NULL)
509 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200510 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200511 {
512 return FALSE;
513 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200514
515 if (isset($params['hmac_digest']))
516 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200517 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200518 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200519 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200520 ? $this->_digests[$params['hmac_digest']] * 2
521 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200522
Andrey Andreev2da35502014-07-07 14:41:57 +0300523 if (self::strlen($data) <= $digest_size)
Andrey Andreev7c554482014-02-06 02:38:27 +0200524 {
525 return FALSE;
526 }
527
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300528 $hmac_input = self::substr($data, 0, $digest_size);
529 $data = self::substr($data, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200530
Andrey Andreev7c554482014-02-06 02:38:27 +0200531 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
532 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
533
534 // Time-attack-safe comparison
535 $diff = 0;
536 for ($i = 0; $i < $digest_size; $i++)
537 {
538 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
539 }
540
541 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200542 {
543 return FALSE;
544 }
545 }
546
547 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200548 {
549 $data = base64_decode($data);
550 }
551
Andrey Andreev2da35502014-07-07 14:41:57 +0300552 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev81e10642014-02-07 01:43:36 +0200553
Andrey Andreev818aedb2014-02-03 11:30:25 +0200554 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
555 }
556
557 // --------------------------------------------------------------------
558
559 /**
560 * Decrypt via MCrypt
561 *
562 * @param string $data Encrypted data
563 * @param array $params Input parameters
564 * @return string
565 */
566 protected function _mcrypt_decrypt($data, $params)
567 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200568 if ( ! is_resource($params['handle']))
569 {
570 return FALSE;
571 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300572
573 // The greater-than-1 comparison is mostly a work-around for a bug,
574 // where 1 is returned for ARCFour instead of 0.
575 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200576 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300577 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreeve7516b02014-02-05 13:45:31 +0200578 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300579 $iv = self::substr($data, 0, $iv_size);
580 $data = self::substr($data, $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200581 }
582 else
583 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300584 // MCrypt is dumb and this is ignored, only size matters
585 $iv = str_repeat("\x0", $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200586 }
587 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300588 else
589 {
590 $iv = NULL;
591 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200592
Andrey Andreev1e83d692014-06-19 20:08:59 +0300593 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200594 {
595 if ($params['handle'] !== $this->_handle)
596 {
597 mcrypt_module_close($params['handle']);
598 }
599
600 return FALSE;
601 }
602
603 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200604 // Remove PKCS#7 padding, if necessary
605 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
606 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300607 $data = self::substr($data, 0, -ord($data[self::strlen($data)-1]));
Andrey Andreevf4017672014-02-05 18:51:15 +0200608 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200609
610 mcrypt_generic_deinit($params['handle']);
611 if ($params['handle'] !== $this->_handle)
612 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200613 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200614 }
615
Andrey Andreevf4017672014-02-05 18:51:15 +0200616 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200617 }
618
619 // --------------------------------------------------------------------
620
621 /**
622 * Decrypt via OpenSSL
623 *
624 * @param string $data Encrypted data
625 * @param array $params Input parameters
626 * @return string
627 */
628 protected function _openssl_decrypt($data, $params)
629 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300630 if ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreeve7516b02014-02-05 13:45:31 +0200631 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300632 $iv = self::substr($data, 0, $iv_size);
633 $data = self::substr($data, $iv_size);
Andrey Andreev1e83d692014-06-19 20:08:59 +0300634 }
635 else
636 {
637 $iv = NULL;
Andrey Andreeve7516b02014-02-05 13:45:31 +0200638 }
639
Andrey Andreev29cade42014-02-04 14:05:58 +0200640 return empty($params['handle'])
641 ? FALSE
642 : openssl_decrypt(
643 $data,
644 $params['handle'],
645 $params['key'],
646 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300647 $iv
Andrey Andreev29cade42014-02-04 14:05:58 +0200648 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200649 }
650
651 // --------------------------------------------------------------------
652
653 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200654 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200655 *
656 * @param array $params Input parameters
657 * @return array
658 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200659 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200660 {
661 if (empty($params))
662 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200663 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200664 ? array(
665 'handle' => $this->_handle,
666 'cipher' => $this->_cipher,
667 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200668 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200669 'base64' => TRUE,
Andrey Andreevab9971f2014-07-02 19:09:08 +0300670 'hmac_digest' => 'sha512',
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200671 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200672 )
673 : FALSE;
674 }
675 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
676 {
677 return FALSE;
678 }
679
Andrey Andreevf4017672014-02-05 18:51:15 +0200680 if (isset($params['mode']))
681 {
682 $params['mode'] = strtolower($params['mode']);
683 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
684 {
685 return FALSE;
686 }
687 else
688 {
689 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
690 }
691 }
692
Andrey Andreevab9971f2014-07-02 19:09:08 +0300693 if (isset($params['hmac']) && $params['hmac'] === FALSE)
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200694 {
695 $params['hmac_digest'] = $params['hmac_key'] = NULL;
696 }
697 else
698 {
699 if ( ! isset($params['hmac_key']))
700 {
701 return FALSE;
702 }
703 elseif (isset($params['hmac_digest']))
704 {
705 $params['hmac_digest'] = strtolower($params['hmac_digest']);
706 if ( ! isset($this->_digests[$params['hmac_digest']]))
707 {
708 return FALSE;
709 }
710 }
711 else
712 {
713 $params['hmac_digest'] = 'sha512';
714 }
715 }
716
Andrey Andreev818aedb2014-02-03 11:30:25 +0200717 $params = array(
718 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200719 'cipher' => $params['cipher'],
720 'mode' => $params['mode'],
721 'key' => $params['key'],
Andrey Andreev4b450652014-02-10 06:59:54 +0200722 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200723 'hmac_digest' => $params['hmac_digest'],
724 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200725 );
726
Andrey Andreev818aedb2014-02-03 11:30:25 +0200727 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200728 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
729 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
730 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200731
732 return $params;
733 }
734
735 // --------------------------------------------------------------------
736
737 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200738 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200739 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200740 * @param string $cipher Cipher name
741 * @param string $mode Encryption mode
742 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200743 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200744 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200745 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200746 return mcrypt_module_open($cipher, '', $mode, '');
747 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200748
Andrey Andreev29cade42014-02-04 14:05:58 +0200749 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200750
Andrey Andreev29cade42014-02-04 14:05:58 +0200751 /**
752 * Get OpenSSL handle
753 *
754 * @param string $cipher Cipher name
755 * @param string $mode Encryption mode
756 * @return string
757 */
758 protected function _openssl_get_handle($cipher, $mode)
759 {
760 // OpenSSL methods aren't suffixed with '-stream' for this mode
761 return ($mode === 'stream')
762 ? $cipher
763 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200764 }
765
766 // --------------------------------------------------------------------
767
768 /**
769 * Cipher alias
770 *
771 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
772 *
773 * @param string $cipher Cipher name
774 * @return void
775 */
776 protected function _cipher_alias(&$cipher)
777 {
778 static $dictionary;
779
780 if (empty($dictionary))
781 {
782 $dictionary = array(
783 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200784 'aes-128' => 'rijndael-128',
785 'aes-192' => 'rijndael-128',
786 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200787 'des3-ede3' => 'tripledes',
788 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200789 'cast5' => 'cast-128',
790 'rc4' => 'arcfour',
791 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200792 ),
793 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200794 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200795 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200796 'blowfish' => 'bf',
797 'cast-128' => 'cast5',
798 'arcfour' => 'rc4-40',
799 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200800 )
801 );
802
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200803 // Notes:
804 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200805 // - Rijndael-128 is, at the same time all three of AES-128,
806 // AES-192 and AES-256. The only difference between them is
807 // the key size. Rijndael-192, Rijndael-256 on the other hand
808 // also have different block sizes and are NOT AES-compatible.
809 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200810 // - Blowfish is said to be supporting key sizes between
811 // 4 and 56 bytes, but it appears that between MCrypt and
812 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200813 // Also, don't know what MCrypt's 'blowfish-compat' is.
814 //
815 // - CAST-128/CAST5 produces a longer cipher when encrypted via
816 // OpenSSL, but (strangely enough) can be decrypted by either
817 // extension anyway.
Andrey Andreev18767e32014-03-04 22:21:35 +0200818 // Also, it appears that OpenSSL uses 16 rounds regardless of
819 // the key size, while RFC2144 says that for key sizes lower
820 // than 11 bytes, only 12 rounds should be used. This makes
821 // it portable only with keys of between 11 and 16 bytes.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200822 //
823 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
824 // Its 'rc4-40' cipher method seems to work flawlessly, yet
825 // there's another one, 'rc4' that only works with a 16-byte key.
826 //
827 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200828 //
829 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200830 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200831 // - RC2 is NOT compatible and only an obscure forum post
832 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200833 }
834
Andrey Andreev50ccc382014-02-04 23:30:06 +0200835 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200836 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200837 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200838 }
839 }
840
841 // --------------------------------------------------------------------
842
843 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200844 * HKDF
845 *
846 * @link https://tools.ietf.org/rfc/rfc5869.txt
847 * @param $key Input key
848 * @param $digest A SHA-2 hashing algorithm
849 * @param $salt Optional salt
Andrey Andreev29cade42014-02-04 14:05:58 +0200850 * @param $length Output length (defaults to the selected digest size)
Andrey Andreev4b450652014-02-10 06:59:54 +0200851 * @param $info Optional context/application-specific info
Andrey Andreev29cade42014-02-04 14:05:58 +0200852 * @return string A pseudo-random key
853 */
854 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
855 {
856 if ( ! isset($this->_digests[$digest]))
857 {
858 return FALSE;
859 }
860
861 if (empty($length) OR ! is_int($length))
862 {
863 $length = $this->_digests[$digest];
864 }
865 elseif ($length > (255 * $this->_digests[$digest]))
866 {
867 return FALSE;
868 }
869
Andrey Andreev2da35502014-07-07 14:41:57 +0300870 self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
Andrey Andreev29cade42014-02-04 14:05:58 +0200871
872 $prk = hash_hmac($digest, $key, $salt, TRUE);
873 $key = '';
Andrey Andreev2da35502014-07-07 14:41:57 +0300874 for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++)
Andrey Andreev29cade42014-02-04 14:05:58 +0200875 {
876 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
877 $key .= $key_block;
878 }
879
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300880 return self::substr($key, 0, $length);
Andrey Andreev29cade42014-02-04 14:05:58 +0200881 }
882
883 // --------------------------------------------------------------------
884
885 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200886 * __get() magic
887 *
888 * @param string $key Property name
889 * @return mixed
890 */
891 public function __get($key)
892 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200893 // Because aliases
894 if ($key === 'mode')
895 {
896 return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);
897 }
898 elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE))
899 {
900 return $this->{'_'.$key};
901 }
902
903 return NULL;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200904 }
905
Andrey Andreev2da35502014-07-07 14:41:57 +0300906 // --------------------------------------------------------------------
907
908 /**
909 * Byte-safe strlen()
910 *
911 * @param string $str
912 * @return integer
913 */
914 protected static function strlen($str)
915 {
916 return (self::$func_override)
917 ? mb_strlen($str, '8bit')
918 : strlen($str);
919 }
920
921 // --------------------------------------------------------------------
922
923 /**
924 * Byte-safe substr()
925 *
926 * @param string $str
927 * @param int $start
928 * @param int $length
929 * @return string
930 */
Andrey Andreev35a7b442014-07-12 21:46:26 +0300931 protected static function substr($str, $start, $length = NULL)
Andrey Andreev2da35502014-07-07 14:41:57 +0300932 {
933 if (self::$func_override)
934 {
Andrey Andreev35a7b442014-07-12 21:46:26 +0300935 // mb_substr($str, $start, null, '8bit') returns an empty
936 // string on PHP 5.3
937 isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
Andrey Andreev11beac22014-07-12 18:28:38 +0300938 return mb_substr($str, $start, $length, '8bit');
Andrey Andreev2da35502014-07-07 14:41:57 +0300939 }
940
941 return isset($length)
942 ? substr($str, $start, $length)
943 : substr($str, $start);
944 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200945}