blob: 76569f2814f4b212755ff338557e169fc192b2ce [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
84 // --------------------------------------------------------------------
85
86 /**
87 * Class constructor
88 *
89 * @param array $params Configuration parameters
90 * @return void
91 */
92 public function __construct(array $params = array())
93 {
94 $this->_drivers = array(
95 'mcrypt' => extension_loaded('mcrypt'),
96 // While OpenSSL is available for PHP 5.3.0, an IV parameter
97 // for the encrypt/decrypt functions is only available since 5.3.3
98 'openssl' => (is_php('5.3.3') && extension_loaded('openssl'))
99 );
100
101 if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl'])
102 {
103 return show_error('Encryption: Unable to find an available encryption driver.');
104 }
105
106 $this->initialize($params);
107
108 if (empty($this->_driver))
109 {
110 $this->_driver = ($this->_drivers['mcrypt'] === TRUE)
111 ? 'mcrypt'
112 : 'openssl';
113
114 log_message('debug', "Encryption: Auto-configured driver '".$params['driver']."'.");
115 }
116
117 isset($this->_key) OR $this->_key = config_item('encryption_key');
118 if (empty($this->_key))
119 {
120 return show_error('Encryption: You are required to set an encryption key in your configuration.');
121 }
122
123 log_message('debug', 'Encryption Class Initialized');
124 }
125
126 // --------------------------------------------------------------------
127
128 /**
129 * Initialize
130 *
131 * @param array $params Configuration parameters
132 * @return CI_Encryption
133 */
134 public function initialize(array $params)
135 {
136 if ( ! empty($params['driver']))
137 {
138 if (isset($this->_drivers[$params['driver']]))
139 {
140 if ($this->_driver[$params['driver']])
141 {
142 $this->_driver = $params['driver'];
143 }
144 else
145 {
146 log_message('error', "Encryption: Driver '".$params['driver']."' is not available.");
147 }
148 }
149 else
150 {
151 log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured.");
152 }
153 }
154
155 empty($params['key']) OR $this->_key = $params['key'];
156 $this->{'_'.$this->_driver.'_initialize'}($params);
157 return $this;
158 }
159
160 // --------------------------------------------------------------------
161
162 /**
163 * Initialize MCrypt
164 *
165 * @param array $params Configuration parameters
166 * @return void
167 */
168 protected function _mcrypt_initialize($params)
169 {
170 if ( ! empty($params['cipher']))
171 {
172 $params['cipher'] = strtolower($params['cipher']);
173 $this->_cipher_alias($params['cipher']);
174
175 if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE))
176 {
177 log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.');
178 }
179 else
180 {
181 $this->_cipher = $params['cipher'];
182 }
183 }
184
185 if ( ! empty($params['mode']))
186 {
187 if ( ! defined('MCRYPT_MODE_'.$params['mode']))
188 {
189 log_message('error', 'Encryption: MCrypt mode '.strtotupper($params['mode']).' is not available.');
190 }
191 else
192 {
193 $this->_mode = constant('MCRYPT_MODE_'.$params['mode']);
194 }
195 }
196
197 if (isset($this->_cipher, $this->_mode))
198 {
199 if (is_resource($this->_handle)
200 && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher
201 OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode)
202 )
203 {
204 mcrypt_module_close($this->_handle);
205 }
206
207 if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, ''))
208 {
209 log_message('debug', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.');
210 }
211 else
212 {
213 log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.');
214 }
215 }
216 }
217
218 // --------------------------------------------------------------------
219
220 /**
221 * Initialize OpenSSL
222 *
223 * @param array $params Configuration parameters
224 * @return void
225 */
226 protected function _openssl_initialize($params)
227 {
228 if ( ! empty($params['cipher']))
229 {
230 $params['cipher'] = strtolower($params['cipher']);
231 $this->_cipher_alias($params['cipher']);
232 $this->_cipher = $params['cipher'];
233 }
234
235 if ( ! empty($params['mode']))
236 {
237 $this->_mode = strtolower($params['mode']);
238 }
239
240 if (isset($this->_cipher, $this->_mode))
241 {
242 // OpenSSL methods aren't suffixed with '-stream' for this mode
243 $handle = ($this->_mode === 'stream')
244 ? $this->_cipher
245 : $this->_cipher.'-'.$this->_mode;
246
247 if ( ! in_array($handle, openssl_get_cipher_methods, TRUE))
248 {
249 $this->_handle = NULL;
250 log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.');
251 }
252 else
253 {
254 $this->_handle = $handle;
255 log_message('debug', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.');
256 }
257 }
258 }
259
260 // --------------------------------------------------------------------
261
262 /**
263 * Encrypt
264 *
265 * @param string $data Input data
266 * @param array $params Input parameters
267 * @return string
268 */
269 public function encrypt($data, array $params = NULL)
270 {
271 if (($params = $this->{'_'.$this->_driver.'_get_params'}($params)) === FALSE)
272 {
273 return FALSE;
274 }
275 elseif ( ! isset($params['iv']))
276 {
277 $params['iv'] = ($iv_size = $this->{'_'.$this->_driver.'_get_iv_size'}($params['handle']))
278 ? $this->{'_'.$this->_driver.'_get_iv'}($iv_size)
279 : NULL;
280 }
281
282 if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
283 {
284 return FALSE;
285 }
286
287 return ($params['base64'])
288 ? base64_encode($data)
289 : $data;
290 }
291
292 // --------------------------------------------------------------------
293
294 /**
295 * Encrypt via MCrypt
296 *
297 * @param string $data Input data
298 * @param array $params Input parameters
299 * @return string
300 */
301 protected function _mcrypt_encrypt($data, $params)
302 {
303 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
304 {
305 if ($params['handle'] !== $this->_handle)
306 {
307 mcrypt_module_close($params['handle']);
308 }
309
310 return FALSE;
311 }
312
313 // Use PKCS#7 padding in order to ensure compatibility with OpenSSL
314 // and other implementations outside of PHP
315 $block_size = mcrypt_enc_get_block_size($params['handle']);
316 $pad = $block_size - (strlen($data) % $block_size);
317 $data .= str_repeat(chr($pad), $pad);
318
319 $data = $params['iv'].mcrypt_generic($params['handle'], $data);
320
321 mcrypt_generic_deinit($params['handle']);
322 if ($params['handle'] !== $this->_handle)
323 {
324 mcrypt_module_close($handle);
325 }
326
327 return $data;
328 }
329
330 // --------------------------------------------------------------------
331
332 /**
333 * Encrypt via OpenSSL
334 *
335 * @param string $data Input data
336 * @param array $params Input parameters
337 * @return string
338 */
339 protected function _openssl_encrypt($data, $params)
340 {
341 $data = openssl_encrypt(
342 $data,
343 $params['handle'],
344 $params['key'],
345 1, // DO NOT TOUCH!
346 $params['iv']
347 );
348
349 if ($data === FALSE)
350 {
351 return FALSE;
352 }
353
354 return $params['iv'].$data;
355 }
356
357 // --------------------------------------------------------------------
358
359 /**
360 * Decrypt
361 *
362 * @param string $data Encrypted data
363 * @param array $params Input parameters
364 * @return string
365 */
366 public function decrypt($data, array $params = NULL)
367 {
368 if (($params = $this->{'_'.$this->_driver.'_get_params'}($params)) === FALSE)
369 {
370 return FALSE;
371 }
372 elseif ($params['base64'])
373 {
374 $data = base64_decode($data);
375 }
376
377 if ( ! isset($params['iv']))
378 {
379 if ($iv_size)
380 {
381 $params['iv'] = substr($data, 0, $iv_size);
382 $data = substr($data, $iv_size);
383 }
384 else
385 {
386 $params['iv'] = NULL;
387 }
388 }
389 elseif (strncmp($params['iv'], $data, $iv_size = strlen($params['iv'])) === 0)
390 {
391 $data = substr($data, $iv_size);
392 }
393
394 return $this->{'_'.$this->_driver.'_decrypt'}($data, $params);
395 }
396
397 // --------------------------------------------------------------------
398
399 /**
400 * Decrypt via MCrypt
401 *
402 * @param string $data Encrypted data
403 * @param array $params Input parameters
404 * @return string
405 */
406 protected function _mcrypt_decrypt($data, $params)
407 {
408 if (mcrypt_generic_init($params['handle'], $params['key'], $params['iv']) < 0)
409 {
410 if ($params['handle'] !== $this->_handle)
411 {
412 mcrypt_module_close($params['handle']);
413 }
414
415 return FALSE;
416 }
417
418 $data = mdecrypt_generic($params['handle'], $data);
419
420 mcrypt_generic_deinit($params['handle']);
421 if ($params['handle'] !== $this->_handle)
422 {
423 mcrypt_module_close($handle);
424 }
425
426 // Remove PKCS#7 padding
427 return substr($data, 0, -ord($data[strlen($data)-1]));
428 }
429
430 // --------------------------------------------------------------------
431
432 /**
433 * Decrypt via OpenSSL
434 *
435 * @param string $data Encrypted data
436 * @param array $params Input parameters
437 * @return string
438 */
439 protected function _openssl_decrypt($data, $params)
440 {
441 return openssl_decrypt(
442 $data,
443 $params['handle'],
444 $params['key'],
445 1, // DO NOT TOUCH!
446 $params['iv']
447 );
448 }
449
450 // --------------------------------------------------------------------
451
452 /**
453 * Get IV size via MCrypt
454 *
455 * @param resource $handle MCrypt module resource
456 * @return int
457 */
458 protected function _mcrypt_get_iv_size($handle)
459 {
460 return mcrypt_enc_get_iv_size($handle);
461 }
462
463 // --------------------------------------------------------------------
464
465 /**
466 * Get IV size via OpenSSL
467 *
468 * @param string $handle OpenSSL cipher method
469 * @return int
470 */
471 protected function _openssl_get_iv_size($handle)
472 {
473 return openssl_cipher_iv_length($handle);
474 }
475
476 // --------------------------------------------------------------------
477
478 /**
479 * Get IV via MCrypt
480 *
481 * @param int $size
482 * @return int
483 */
484 protected function _mcrypt_get_iv($size)
485 {
486 // If /dev/urandom is available - use it, otherwise there's
487 // also /dev/random, but it is highly unlikely that it would
488 // be available while /dev/urandom is not and it is known to be
489 // blocking anyway.
490 if (defined(MCRYPT_DEV_URANDOM))
491 {
492 $source = MCRYPT_DEV_URANDOM;
493 }
494 else
495 {
496 $source = MCRYPT_RAND;
497 is_php('5.3') OR srand(microtime(TRUE));
498 }
499
500 return mcrypt_create_iv($size, $source);
501 }
502
503 // --------------------------------------------------------------------
504
505 /**
506 * Get IV via OpenSSL
507 *
508 * @param int $size IV size
509 * @return int
510 */
511 protected function _openssl_get_iv($size)
512 {
513 return openssl_random_pseudo_bytes($size);
514 }
515
516 // --------------------------------------------------------------------
517
518 /**
519 * Get MCrypt parameters
520 *
521 * @param array $params Input parameters
522 * @return array
523 */
524 protected function _mcrypt_get_params($params)
525 {
526 if (empty($params))
527 {
528 if ( ! isset($this->_cipher, $this->_mode, $params->_key, $this->_handle) OR ! is_resource($this->_handle))
529 {
530 return FALSE;
531 }
532
533 return array(
534 'handle' => $this->_handle,
535 'cipher' => $this->_cipher,
536 'mode' => $this->_mode,
537 'key' => $this->_key,
538 'base64' => TRUE
539 );
540 }
541
542 $params = array(
543 'handle' => NULL,
544 'cipher' => isset($params['cipher']) ? $params['cipher'] : $this->_cipher,
545 'mode' => isset($params['mode']) ? $params['mode'] : $this->_mode,
546 'key' => isset($params['key']) ? $params['key'] : $this->_key,
547 'iv' => isset($params['iv']) ? $params['iv'] : NULL,
548 'base64' => isset($params['base64']) ? $params['base64'] : TRUE
549 );
550
551 if ( ! isset($params['cipher'], $params['mode'], $params['key']))
552 {
553 return FALSE;
554 }
555
556 $this->_cipher_alias($params['cipher']);
557
558 if ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
559 {
560 if (($params['handle'] = mcrypt_module_open($params['cipher'], '', $params['mode'], '')) === FALSE)
561 {
562 return FALSE;
563 }
564 }
565 else
566 {
567 if ( ! is_resource($this->_handle))
568 {
569 return FALSE;
570 }
571
572 $params['handle'] = $this->_handle;
573 }
574
575 return $params;
576 }
577
578 // --------------------------------------------------------------------
579
580 /**
581 * Get OpenSSL parameters
582 *
583 * @param array $params Input parameters
584 * @return array
585 */
586 protected function _openssl_get_params($params)
587 {
588 if (empty($params))
589 {
590 if ( ! isset($this->_cipher, $this->_mode, $params->_key, $this->_handle))
591 {
592 return FALSE;
593 }
594
595 return array(
596 'handle' => $this->_handle,
597 'cipher' => $this->_cipher,
598 'mode' => $this->_mode,
599 'key' => $this->_key,
600 'base64' => TRUE
601 );
602 }
603
604 $params = array(
605 'handle' => NULL,
606 'cipher' => isset($params['cipher']) ? $params['cipher'] : $this->_cipher,
607 'mode' => isset($params['mode']) ? $params['mode'] : $this->_mode,
608 'key' => isset($params['key']) ? $params['key'] : $this->_key,
609 'iv' => isset($params['iv']) ? $params['iv'] : NULL,
610 'base64' => isset($params['base64']) ? $params['base64'] : TRUE
611 );
612
613 if ( ! isset($params['cipher'], $params['mode'], $params['key']))
614 {
615 return FALSE;
616 }
617
618 $this->_cipher_alias($params['cipher']);
619
620 if ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode)
621 {
622 // OpenSSL methods aren't suffixed with '-stream' for this mode
623 $params['handle'] = ($params['mode'] === 'stream')
624 ? $params['cipher']
625 : $params['cipher'].'-'.$params['mode'];
626 $params['handle'] = $params['cipher'].'-'.$params['mode'];
627 }
628 else
629 {
630 if (empty($this->_handle))
631 {
632 return FALSE;
633 }
634
635 $params['handle'] = $this->_handle;
636 }
637
638 return $params;
639 }
640
641 // --------------------------------------------------------------------
642
643 /**
644 * Cipher alias
645 *
646 * Tries to translate cipher names between MCrypt and OpenSSL's "dialects".
647 *
648 * @param string $cipher Cipher name
649 * @return void
650 */
651 protected function _cipher_alias(&$cipher)
652 {
653 static $dictionary;
654
655 if (empty($dictionary))
656 {
657 $dictionary = array(
658 'mcrypt' => array(
659 'rijndael-128',
660 'tripledes',
661 'arcfour'
662 ),
663 'openssl' => array(
664 'aes-128',
665 'des-ede3',
666 'rc4-40'
667 )
668 );
669
670 // Notes regarding other seemingly matching ciphers between
671 // MCrypt and OpenSSL:
672 //
673 // - DES is compatible, but doesn't need an alias
674 // - Blowfish is NOT compatible
675 // mcrypt: 'blowfish', 'blowfish-compat'
676 // openssl: 'bf'
677 // - CAST-128/CAST5 is NOT compatible
678 // mcrypt: 'cast-128'
679 // openssl: 'cast5'
680 // - RC2 is NOT compatible
681 // mcrypt: 'rc2'
682 // openssl: 'rc2', 'rc2-40', 'rc2-64'
683 //
684 // To avoid any other confusion due to a popular (but incorrect)
685 // belief, it should also be noted that Rijndael-192/256 are NOT
686 // the same ciphers as AES-192/256 like Rijndael-128 and AES-256 is.
687 //
688 // All compatibility tests were done in CBC mode.
689 }
690
691 $dialect = ($this->_driver === 'mcrypt')
692 ? 'openssl'
693 : 'mcrypt';
694 if (($index = array_search($cipher, $dictionary[$dialect], TRUE)) !== FALSE)
695 {
696 $cipher = $dictionary[$this->_driver][$index];
697 }
698 }
699
700 // --------------------------------------------------------------------
701
702 /**
703 * __get() magic
704 *
705 * @param string $key Property name
706 * @return mixed
707 */
708 public function __get($key)
709 {
710 return in_array($key, array('cipher', 'mode', 'driver', 'drivers'), TRUE)
711 ? $this->{'_'.$key}
712 : NULL;
713 }
714
715}
716
717/* End of file Encryption.php */
718/* Location: ./system/libraries/Encryption.php */