blob: 9a21bae4cfb11c76404b42e6fbe0fa31b3d03da3 [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 *
Master Yodada60e9b2016-12-31 08:46:18 -08009 * Copyright (c) 2014 - 2017, 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/)
Master Yodada60e9b2016-12-31 08:46:18 -080032 * @copyright Copyright (c) 2014 - 2017, 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 /**
Andrey Andreevf5652122017-01-19 15:17:00 +0200138 * mbstring.func_overload flag
Andrey Andreev2da35502014-07-07 14:41:57 +0300139 *
140 * @var bool
141 */
Andrey Andreevf5652122017-01-19 15:17:00 +0200142 protected static $func_overload;
Andrey Andreev2da35502014-07-07 14:41:57 +0300143
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 Andreeva8382792016-07-28 16:40:12 +0300155 'mcrypt' => defined('MCRYPT_DEV_URANDOM'),
156 'openssl' => extension_loaded('openssl')
Andrey Andreev818aedb2014-02-03 11:30:25 +0200157 );
158
159 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
160 {
Gabriel Potkány1fb50002015-02-04 01:45:59 +0100161 show_error('Encryption: Unable to find an available encryption driver.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200162 }
163
Andrey Andreevf5652122017-01-19 15:17:00 +0200164 isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
Andrey Andreev818aedb2014-02-03 11:30:25 +0200165 $this->initialize($params);
Andrey Andreev2da35502014-07-07 14:41:57 +0300166
167 if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200168 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200169 $this->_key = $key;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200170 }
171
Andrey Andreev90726b82015-01-20 12:39:22 +0200172 log_message('info', 'Encryption Class Initialized');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200173 }
174
175 // --------------------------------------------------------------------
176
177 /**
178 * Initialize
179 *
180 * @param array $params Configuration parameters
181 * @return CI_Encryption
182 */
183 public function initialize(array $params)
184 {
185 if ( ! empty($params['driver']))
186 {
187 if (isset($this->_drivers[$params['driver']]))
188 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200189 if ($this->_drivers[$params['driver']])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200190 {
191 $this->_driver = $params['driver'];
192 }
193 else
194 {
195 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
196 }
197 }
198 else
199 {
200 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
201 }
202 }
203
Andrey Andreev912831f2014-02-04 17:21:37 +0200204 if (empty($this->_driver))
205 {
Andrey Andreev8e202162014-02-05 18:59:55 +0200206 $this->_driver = ($this->_drivers['openssl'] === TRUE)
207 ? 'openssl'
208 : 'mcrypt';
Andrey Andreev912831f2014-02-04 17:21:37 +0200209
210 log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'.");
211 }
212
Andrey Andreev50c9ea12014-11-07 16:57:41 +0200213 empty($params['cipher']) && $params['cipher'] = $this->_cipher;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200214 empty($params['key']) OR $this->_key = $params['key'];
215 $this->{'_'.$this->_driver.'_initialize'}($params);
216 return $this;
217 }
218
219 // --------------------------------------------------------------------
220
221 /**
222 * Initialize MCrypt
223 *
224 * @param array $params Configuration parameters
225 * @return void
226 */
227 protected function _mcrypt_initialize($params)
228 {
229 if ( ! empty($params['cipher']))
230 {
231 $params['cipher'] = strtolower($params['cipher']);
232 $this->_cipher_alias($params['cipher']);
233
234 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
235 {
236 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
237 }
238 else
239 {
240 $this->_cipher = $params['cipher'];
241 }
242 }
243
244 if ( ! empty($params['mode']))
245 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200246 $params['mode'] = strtolower($params['mode']);
247 if ( ! isset($this->_modes['mcrypt'][$params['mode']]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200248 {
Gabriel Potkány8194ce52015-02-04 10:34:24 +0100249 log_message('error', 'Encryption: MCrypt mode '.strtoupper($params['mode']).' is not available.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200250 }
251 else
252 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200253 $this->_mode = $this->_modes['mcrypt'][$params['mode']];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200254 }
255 }
256
257 if (isset($this->_cipher, $this->_mode))
258 {
259 if (is_resource($this->_handle)
260 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
261 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
262 )
263 {
264 mcrypt_module_close($this->_handle);
265 }
266
267 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
268 {
Andrey Andreev90726b82015-01-20 12:39:22 +0200269 log_message('info', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200270 }
271 else
272 {
273 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
274 }
275 }
276 }
277
278 // --------------------------------------------------------------------
279
280 /**
281 * Initialize OpenSSL
282 *
283 * @param array $params Configuration parameters
284 * @return void
285 */
286 protected function _openssl_initialize($params)
287 {
288 if ( ! empty($params['cipher']))
289 {
290 $params['cipher'] = strtolower($params['cipher']);
291 $this->_cipher_alias($params['cipher']);
292 $this->_cipher = $params['cipher'];
293 }
294
295 if ( ! empty($params['mode']))
296 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200297 $params['mode'] = strtolower($params['mode']);
298 if ( ! isset($this->_modes['openssl'][$params['mode']]))
299 {
Gabriel Potkány8194ce52015-02-04 10:34:24 +0100300 log_message('error', 'Encryption: OpenSSL mode '.strtoupper($params['mode']).' is not available.');
Andrey Andreevf4017672014-02-05 18:51:15 +0200301 }
302 else
303 {
304 $this->_mode = $this->_modes['openssl'][$params['mode']];
305 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200306 }
307
308 if (isset($this->_cipher, $this->_mode))
309 {
Andrey Andreevf4017672014-02-05 18:51:15 +0200310 // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL
311 $handle = empty($this->_mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200312 ? $this->_cipher
313 : $this->_cipher.'-'.$this->_mode;
314
Andrey Andreev50ccc382014-02-04 23:30:06 +0200315 if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200316 {
317 $this->_handle = NULL;
318 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
319 }
320 else
321 {
322 $this->_handle = $handle;
Andrey Andreev90726b82015-01-20 12:39:22 +0200323 log_message('info', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200324 }
325 }
326 }
327
328 // --------------------------------------------------------------------
329
330 /**
Andrey Andreev42183de2014-06-22 00:09:36 +0300331 * Create a random key
332 *
333 * @param int $length Output length
334 * @return string
335 */
336 public function create_key($length)
337 {
Andrey Andreev5afa3482015-11-24 11:48:39 +0200338 if (function_exists('random_bytes'))
339 {
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200340 try
341 {
342 return random_bytes((int) $length);
343 }
344 catch (Exception $e)
345 {
346 log_message('error', $e->getMessage());
347 return FALSE;
348 }
349 }
350 elseif (defined('MCRYPT_DEV_URANDOM'))
351 {
352 return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
Andrey Andreev5afa3482015-11-24 11:48:39 +0200353 }
354
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200355 $is_secure = NULL;
356 $key = openssl_random_pseudo_bytes($length, $is_secure);
357 return ($is_secure === TRUE)
358 ? $key
359 : FALSE;
Andrey Andreev42183de2014-06-22 00:09:36 +0300360 }
361
362 // --------------------------------------------------------------------
363
364 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200365 * Encrypt
366 *
367 * @param string $data Input data
368 * @param array $params Input parameters
369 * @return string
370 */
371 public function encrypt($data, array $params = NULL)
372 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200373 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200374 {
375 return FALSE;
376 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200377
Andrey Andreev2da35502014-07-07 14:41:57 +0300378 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev818aedb2014-02-03 11:30:25 +0200379
380 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
381 {
382 return FALSE;
383 }
384
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200385 $params['base64'] && $data = base64_encode($data);
Andrey Andreev29cade42014-02-04 14:05:58 +0200386
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200387 if (isset($params['hmac_digest']))
Andrey Andreev29cade42014-02-04 14:05:58 +0200388 {
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200389 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
390 return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
Andrey Andreev29cade42014-02-04 14:05:58 +0200391 }
392
393 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200394 }
395
396 // --------------------------------------------------------------------
397
398 /**
399 * Encrypt via MCrypt
400 *
401 * @param string $data Input data
402 * @param array $params Input parameters
403 * @return string
404 */
405 protected function _mcrypt_encrypt($data, $params)
406 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200407 if ( ! is_resource($params['handle']))
408 {
409 return FALSE;
410 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200411
Andrey Andreev1e83d692014-06-19 20:08:59 +0300412 // The greater-than-1 comparison is mostly a work-around for a bug,
413 // where 1 is returned for ARCFour instead of 0.
414 $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200415 ? $this->create_key($iv_size)
Andrey Andreev1e83d692014-06-19 20:08:59 +0300416 : NULL;
417
418 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200419 {
420 if ($params['handle'] !== $this->_handle)
421 {
422 mcrypt_module_close($params['handle']);
423 }
424
425 return FALSE;
426 }
427
428 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
Andrey Andreevf4017672014-02-05 18:51:15 +0200429 // and other implementations outside of PHP.
430 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
431 {
432 $block_size = mcrypt_enc_get_block_size($params['handle']);
Andrey Andreev2da35502014-07-07 14:41:57 +0300433 $pad = $block_size - (self::strlen($data) % $block_size);
Andrey Andreevf4017672014-02-05 18:51:15 +0200434 $data .= str_repeat(chr($pad), $pad);
435 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200436
Andrey Andreeve7516b02014-02-05 13:45:31 +0200437 // Work-around for yet another strange behavior in MCrypt.
438 //
439 // When encrypting in ECB mode, the IV is ignored. Yet
440 // mcrypt_enc_get_iv_size() returns a value larger than 0
441 // even if ECB is used AND mcrypt_generic_init() complains
442 // if you don't pass an IV with length equal to the said
443 // return value.
444 //
445 // This probably would've been fine (even though still wasteful),
446 // but OpenSSL isn't that dumb and we need to make the process
447 // portable, so ...
448 $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreev1e83d692014-06-19 20:08:59 +0300449 ? $iv.mcrypt_generic($params['handle'], $data)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200450 : mcrypt_generic($params['handle'], $data);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200451
452 mcrypt_generic_deinit($params['handle']);
453 if ($params['handle'] !== $this->_handle)
454 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200455 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200456 }
457
458 return $data;
459 }
460
461 // --------------------------------------------------------------------
462
463 /**
464 * Encrypt via OpenSSL
465 *
466 * @param string $data Input data
467 * @param array $params Input parameters
468 * @return string
469 */
470 protected function _openssl_encrypt($data, $params)
471 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200472 if (empty($params['handle']))
473 {
474 return FALSE;
475 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300476
477 $iv = ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreev4d2628e2016-03-22 13:42:03 +0200478 ? $this->create_key($iv_size)
Andrey Andreev1e83d692014-06-19 20:08:59 +0300479 : NULL;
Andrey Andreev29cade42014-02-04 14:05:58 +0200480
Andrey Andreev818aedb2014-02-03 11:30:25 +0200481 $data = openssl_encrypt(
482 $data,
483 $params['handle'],
484 $params['key'],
485 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300486 $iv
Andrey Andreev818aedb2014-02-03 11:30:25 +0200487 );
488
489 if ($data === FALSE)
490 {
491 return FALSE;
492 }
493
Andrey Andreev1e83d692014-06-19 20:08:59 +0300494 return $iv.$data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200495 }
496
497 // --------------------------------------------------------------------
498
499 /**
500 * Decrypt
501 *
502 * @param string $data Encrypted data
503 * @param array $params Input parameters
504 * @return string
505 */
506 public function decrypt($data, array $params = NULL)
507 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200508 if (($params = $this->_get_params($params)) === FALSE)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200509 {
510 return FALSE;
511 }
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200512
513 if (isset($params['hmac_digest']))
514 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200515 // This might look illogical, but it is done during encryption as well ...
Andrey Andreev177144f2014-02-04 18:07:34 +0200516 // The 'base64' value is effectively an inverted "raw data" parameter
Andrey Andreev29cade42014-02-04 14:05:58 +0200517 $digest_size = ($params['base64'])
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200518 ? $this->_digests[$params['hmac_digest']] * 2
519 : $this->_digests[$params['hmac_digest']];
Andrey Andreev7c554482014-02-06 02:38:27 +0200520
Andrey Andreev2da35502014-07-07 14:41:57 +0300521 if (self::strlen($data) <= $digest_size)
Andrey Andreev7c554482014-02-06 02:38:27 +0200522 {
523 return FALSE;
524 }
525
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300526 $hmac_input = self::substr($data, 0, $digest_size);
527 $data = self::substr($data, $digest_size);
Andrey Andreev29cade42014-02-04 14:05:58 +0200528
Andrey Andreev7c554482014-02-06 02:38:27 +0200529 isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
530 $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']);
531
532 // Time-attack-safe comparison
533 $diff = 0;
534 for ($i = 0; $i < $digest_size; $i++)
535 {
536 $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]);
537 }
538
539 if ($diff !== 0)
Andrey Andreev29cade42014-02-04 14:05:58 +0200540 {
541 return FALSE;
542 }
543 }
544
545 if ($params['base64'])
Andrey Andreev818aedb2014-02-03 11:30:25 +0200546 {
547 $data = base64_decode($data);
548 }
549
Andrey Andreev2da35502014-07-07 14:41:57 +0300550 isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
Andrey Andreev81e10642014-02-07 01:43:36 +0200551
Andrey Andreev818aedb2014-02-03 11:30:25 +0200552 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
553 }
554
555 // --------------------------------------------------------------------
556
557 /**
558 * Decrypt via MCrypt
559 *
560 * @param string $data Encrypted data
561 * @param array $params Input parameters
562 * @return string
563 */
564 protected function _mcrypt_decrypt($data, $params)
565 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200566 if ( ! is_resource($params['handle']))
567 {
568 return FALSE;
569 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300570
571 // The greater-than-1 comparison is mostly a work-around for a bug,
572 // where 1 is returned for ARCFour instead of 0.
573 if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
Andrey Andreeve7516b02014-02-05 13:45:31 +0200574 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300575 if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
Andrey Andreeve7516b02014-02-05 13:45:31 +0200576 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300577 $iv = self::substr($data, 0, $iv_size);
578 $data = self::substr($data, $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200579 }
580 else
581 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300582 // MCrypt is dumb and this is ignored, only size matters
583 $iv = str_repeat("\x0", $iv_size);
Andrey Andreeve7516b02014-02-05 13:45:31 +0200584 }
585 }
Andrey Andreev1e83d692014-06-19 20:08:59 +0300586 else
587 {
588 $iv = NULL;
589 }
Andrey Andreeve7516b02014-02-05 13:45:31 +0200590
Andrey Andreev1e83d692014-06-19 20:08:59 +0300591 if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200592 {
593 if ($params['handle'] !== $this->_handle)
594 {
595 mcrypt_module_close($params['handle']);
596 }
597
598 return FALSE;
599 }
600
601 $data = mdecrypt_generic($params['handle'], $data);
Andrey Andreevf4017672014-02-05 18:51:15 +0200602 // Remove PKCS#7 padding, if necessary
603 if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
604 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300605 $data = self::substr($data, 0, -ord($data[self::strlen($data)-1]));
Andrey Andreevf4017672014-02-05 18:51:15 +0200606 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200607
608 mcrypt_generic_deinit($params['handle']);
609 if ($params['handle'] !== $this->_handle)
610 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200611 mcrypt_module_close($params['handle']);
Andrey Andreev818aedb2014-02-03 11:30:25 +0200612 }
613
Andrey Andreevf4017672014-02-05 18:51:15 +0200614 return $data;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200615 }
616
617 // --------------------------------------------------------------------
618
619 /**
620 * Decrypt via OpenSSL
621 *
622 * @param string $data Encrypted data
623 * @param array $params Input parameters
624 * @return string
625 */
626 protected function _openssl_decrypt($data, $params)
627 {
Andrey Andreev1e83d692014-06-19 20:08:59 +0300628 if ($iv_size = openssl_cipher_iv_length($params['handle']))
Andrey Andreeve7516b02014-02-05 13:45:31 +0200629 {
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300630 $iv = self::substr($data, 0, $iv_size);
631 $data = self::substr($data, $iv_size);
Andrey Andreev1e83d692014-06-19 20:08:59 +0300632 }
633 else
634 {
635 $iv = NULL;
Andrey Andreeve7516b02014-02-05 13:45:31 +0200636 }
637
Andrey Andreev29cade42014-02-04 14:05:58 +0200638 return empty($params['handle'])
639 ? FALSE
640 : openssl_decrypt(
641 $data,
642 $params['handle'],
643 $params['key'],
644 1, // DO NOT TOUCH!
Andrey Andreev1e83d692014-06-19 20:08:59 +0300645 $iv
Andrey Andreev29cade42014-02-04 14:05:58 +0200646 );
Andrey Andreev818aedb2014-02-03 11:30:25 +0200647 }
648
649 // --------------------------------------------------------------------
650
651 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200652 * Get params
Andrey Andreev818aedb2014-02-03 11:30:25 +0200653 *
654 * @param array $params Input parameters
655 * @return array
656 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200657 protected function _get_params($params)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200658 {
659 if (empty($params))
660 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200661 return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle)
Andrey Andreev29cade42014-02-04 14:05:58 +0200662 ? array(
663 'handle' => $this->_handle,
664 'cipher' => $this->_cipher,
665 'mode' => $this->_mode,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200666 'key' => NULL,
Andrey Andreev29cade42014-02-04 14:05:58 +0200667 'base64' => TRUE,
Andrey Andreevab9971f2014-07-02 19:09:08 +0300668 'hmac_digest' => 'sha512',
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200669 'hmac_key' => NULL
Andrey Andreev29cade42014-02-04 14:05:58 +0200670 )
671 : FALSE;
672 }
673 elseif ( ! isset($params['cipher'], $params['mode'], $params['key']))
674 {
675 return FALSE;
676 }
677
Andrey Andreevf4017672014-02-05 18:51:15 +0200678 if (isset($params['mode']))
679 {
680 $params['mode'] = strtolower($params['mode']);
681 if ( ! isset($this->_modes[$this->_driver][$params['mode']]))
682 {
683 return FALSE;
684 }
Andrey Andreevfbe4d792017-12-27 19:49:03 +0200685
686 $params['mode'] = $this->_modes[$this->_driver][$params['mode']];
Andrey Andreevf4017672014-02-05 18:51:15 +0200687 }
688
Andrey Andreevab9971f2014-07-02 19:09:08 +0300689 if (isset($params['hmac']) && $params['hmac'] === FALSE)
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200690 {
691 $params['hmac_digest'] = $params['hmac_key'] = NULL;
692 }
693 else
694 {
695 if ( ! isset($params['hmac_key']))
696 {
697 return FALSE;
698 }
699 elseif (isset($params['hmac_digest']))
700 {
701 $params['hmac_digest'] = strtolower($params['hmac_digest']);
702 if ( ! isset($this->_digests[$params['hmac_digest']]))
703 {
704 return FALSE;
705 }
706 }
707 else
708 {
709 $params['hmac_digest'] = 'sha512';
710 }
711 }
712
Andrey Andreev818aedb2014-02-03 11:30:25 +0200713 $params = array(
714 'handle' => NULL,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200715 'cipher' => $params['cipher'],
716 'mode' => $params['mode'],
717 'key' => $params['key'],
Andrey Andreev4b450652014-02-10 06:59:54 +0200718 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE,
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200719 'hmac_digest' => $params['hmac_digest'],
720 'hmac_key' => $params['hmac_key']
Andrey Andreev818aedb2014-02-03 11:30:25 +0200721 );
722
Andrey Andreev818aedb2014-02-03 11:30:25 +0200723 $this->_cipher_alias($params['cipher']);
Andrey Andreev29cade42014-02-04 14:05:58 +0200724 $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
725 ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode'])
726 : $this->_handle;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200727
728 return $params;
729 }
730
731 // --------------------------------------------------------------------
732
733 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200734 * Get MCrypt handle
Andrey Andreev818aedb2014-02-03 11:30:25 +0200735 *
Andrey Andreev29cade42014-02-04 14:05:58 +0200736 * @param string $cipher Cipher name
737 * @param string $mode Encryption mode
738 * @return resource
Andrey Andreev818aedb2014-02-03 11:30:25 +0200739 */
Andrey Andreev29cade42014-02-04 14:05:58 +0200740 protected function _mcrypt_get_handle($cipher, $mode)
Andrey Andreev818aedb2014-02-03 11:30:25 +0200741 {
Andrey Andreev29cade42014-02-04 14:05:58 +0200742 return mcrypt_module_open($cipher, '', $mode, '');
743 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200744
Andrey Andreev29cade42014-02-04 14:05:58 +0200745 // --------------------------------------------------------------------
Andrey Andreev818aedb2014-02-03 11:30:25 +0200746
Andrey Andreev29cade42014-02-04 14:05:58 +0200747 /**
748 * Get OpenSSL handle
749 *
750 * @param string $cipher Cipher name
751 * @param string $mode Encryption mode
752 * @return string
753 */
754 protected function _openssl_get_handle($cipher, $mode)
755 {
756 // OpenSSL methods aren't suffixed with '-stream' for this mode
757 return ($mode === 'stream')
758 ? $cipher
759 : $cipher.'-'.$mode;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200760 }
761
762 // --------------------------------------------------------------------
763
764 /**
765 * Cipher alias
766 *
767 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
768 *
769 * @param string $cipher Cipher name
770 * @return void
771 */
772 protected function _cipher_alias(&$cipher)
773 {
774 static $dictionary;
775
776 if (empty($dictionary))
777 {
778 $dictionary = array(
779 'mcrypt' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200780 'aes-128' => 'rijndael-128',
781 'aes-192' => 'rijndael-128',
782 'aes-256' => 'rijndael-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200783 'des3-ede3' => 'tripledes',
784 'bf' => 'blowfish',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200785 'cast5' => 'cast-128',
786 'rc4' => 'arcfour',
787 'rc4-40' => 'arcfour'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200788 ),
789 'openssl' => array(
Andrey Andreev50ccc382014-02-04 23:30:06 +0200790 'rijndael-128' => 'aes-128',
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200791 'tripledes' => 'des-ede3',
Andrey Andreeve8088d62014-02-06 05:01:48 +0200792 'blowfish' => 'bf',
793 'cast-128' => 'cast5',
794 'arcfour' => 'rc4-40',
795 'rc4' => 'rc4-40'
Andrey Andreev818aedb2014-02-03 11:30:25 +0200796 )
797 );
798
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200799 // Notes:
800 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200801 // - Rijndael-128 is, at the same time all three of AES-128,
802 // AES-192 and AES-256. The only difference between them is
803 // the key size. Rijndael-192, Rijndael-256 on the other hand
804 // also have different block sizes and are NOT AES-compatible.
805 //
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200806 // - Blowfish is said to be supporting key sizes between
807 // 4 and 56 bytes, but it appears that between MCrypt and
808 // OpenSSL, only those of 16 and more bytes are compatible.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200809 // Also, don't know what MCrypt's 'blowfish-compat' is.
810 //
811 // - CAST-128/CAST5 produces a longer cipher when encrypted via
812 // OpenSSL, but (strangely enough) can be decrypted by either
813 // extension anyway.
Andrey Andreev18767e32014-03-04 22:21:35 +0200814 // Also, it appears that OpenSSL uses 16 rounds regardless of
815 // the key size, while RFC2144 says that for key sizes lower
816 // than 11 bytes, only 12 rounds should be used. This makes
817 // it portable only with keys of between 11 and 16 bytes.
Andrey Andreeve8088d62014-02-06 05:01:48 +0200818 //
819 // - RC4 (ARCFour) has a strange implementation under OpenSSL.
820 // Its 'rc4-40' cipher method seems to work flawlessly, yet
821 // there's another one, 'rc4' that only works with a 16-byte key.
822 //
823 // - DES is compatible, but doesn't need an alias.
Andrey Andreevd9a48da2014-02-05 14:10:28 +0200824 //
825 // Other seemingly matching ciphers between MCrypt, OpenSSL:
Andrey Andreev818aedb2014-02-03 11:30:25 +0200826 //
Andrey Andreeve8088d62014-02-06 05:01:48 +0200827 // - RC2 is NOT compatible and only an obscure forum post
828 // confirms that it is MCrypt's fault.
Andrey Andreev818aedb2014-02-03 11:30:25 +0200829 }
830
Andrey Andreev50ccc382014-02-04 23:30:06 +0200831 if (isset($dictionary[$this->_driver][$cipher]))
Andrey Andreev818aedb2014-02-03 11:30:25 +0200832 {
Andrey Andreev50ccc382014-02-04 23:30:06 +0200833 $cipher = $dictionary[$this->_driver][$cipher];
Andrey Andreev818aedb2014-02-03 11:30:25 +0200834 }
835 }
836
837 // --------------------------------------------------------------------
838
839 /**
Andrey Andreev29cade42014-02-04 14:05:58 +0200840 * HKDF
841 *
842 * @link https://tools.ietf.org/rfc/rfc5869.txt
843 * @param $key Input key
844 * @param $digest A SHA-2 hashing algorithm
845 * @param $salt Optional salt
Andrey Andreev29cade42014-02-04 14:05:58 +0200846 * @param $length Output length (defaults to the selected digest size)
Andrey Andreev4b450652014-02-10 06:59:54 +0200847 * @param $info Optional context/application-specific info
Andrey Andreev29cade42014-02-04 14:05:58 +0200848 * @return string A pseudo-random key
849 */
850 public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
851 {
852 if ( ! isset($this->_digests[$digest]))
853 {
854 return FALSE;
855 }
856
857 if (empty($length) OR ! is_int($length))
858 {
859 $length = $this->_digests[$digest];
860 }
861 elseif ($length > (255 * $this->_digests[$digest]))
862 {
863 return FALSE;
864 }
865
Andrey Andreev2da35502014-07-07 14:41:57 +0300866 self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);
Andrey Andreev29cade42014-02-04 14:05:58 +0200867
868 $prk = hash_hmac($digest, $key, $salt, TRUE);
869 $key = '';
Andrey Andreev2da35502014-07-07 14:41:57 +0300870 for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++)
Andrey Andreev29cade42014-02-04 14:05:58 +0200871 {
872 $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
873 $key .= $key_block;
874 }
875
Andrey Andreev9fa275e2014-07-07 14:43:51 +0300876 return self::substr($key, 0, $length);
Andrey Andreev29cade42014-02-04 14:05:58 +0200877 }
878
879 // --------------------------------------------------------------------
880
881 /**
Andrey Andreev818aedb2014-02-03 11:30:25 +0200882 * __get() magic
883 *
884 * @param string $key Property name
885 * @return mixed
886 */
887 public function __get($key)
888 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200889 // Because aliases
890 if ($key === 'mode')
891 {
892 return array_search($this->_mode, $this->_modes[$this->_driver], TRUE);
893 }
894 elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE))
895 {
896 return $this->{'_'.$key};
897 }
898
899 return NULL;
Andrey Andreev818aedb2014-02-03 11:30:25 +0200900 }
901
Andrey Andreev2da35502014-07-07 14:41:57 +0300902 // --------------------------------------------------------------------
903
904 /**
905 * Byte-safe strlen()
906 *
907 * @param string $str
Andrey Andreev8dc32002016-10-03 11:19:49 +0300908 * @return int
Andrey Andreev2da35502014-07-07 14:41:57 +0300909 */
910 protected static function strlen($str)
911 {
Andrey Andreevf5652122017-01-19 15:17:00 +0200912 return (self::$func_overload)
Andrey Andreev2da35502014-07-07 14:41:57 +0300913 ? mb_strlen($str, '8bit')
914 : strlen($str);
915 }
916
917 // --------------------------------------------------------------------
918
919 /**
920 * Byte-safe substr()
921 *
922 * @param string $str
923 * @param int $start
924 * @param int $length
925 * @return string
926 */
Andrey Andreev35a7b442014-07-12 21:46:26 +0300927 protected static function substr($str, $start, $length = NULL)
Andrey Andreev2da35502014-07-07 14:41:57 +0300928 {
Andrey Andreevf5652122017-01-19 15:17:00 +0200929 if (self::$func_overload)
Andrey Andreev2da35502014-07-07 14:41:57 +0300930 {
Andrey Andreev35a7b442014-07-12 21:46:26 +0300931 // mb_substr($str, $start, null, '8bit') returns an empty
932 // string on PHP 5.3
933 isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
Andrey Andreev11beac22014-07-12 18:28:38 +0300934 return mb_substr($str, $start, $length, '8bit');
Andrey Andreev2da35502014-07-07 14:41:57 +0300935 }
936
937 return isset($length)
938 ? substr($str, $start, $length)
939 : substr($str, $start);
940 }
Andrey Andreev818aedb2014-02-03 11:30:25 +0200941}