Completed
Branch FET-9795-new-interfaces (0a2873)
by
unknown
94:13 queued 83:25
created

EE_Encryption::getCipherMethod()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 4
nop 1
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
1
<?php use EventEspresso\core\interfaces\InterminableInterface;
2
3
defined('EVENT_ESPRESSO_VERSION') || exit('No direct script access allowed');
4
5
6
7
/**
8
 * EE_Encryption class
9
 * class for applying low-grade string encryption/decryption
10
 * really only good for hiding content from simple bots and script kiddies
11
 * but better for solving encoding issues with databases
12
 *
13
 * @package    Event Espresso
14
 * @subpackage includes/functions
15
 * @author     Brent Christensen
16
 */
17
class EE_Encryption
18
{
19
20
    /**
21
     * key used for saving the encryption key to the wp_options table
22
     */
23
    const ENCRYPTION_OPTION_KEY = 'ee_encryption_key';
24
25
    /**
26
     * the OPENSSL cipher method used
27
     */
28
    const OPENSSL_CIPHER_METHOD = 'AES-128-CBC';
29
30
    /**
31
     * WP "options_name" used to store a verified available cipher method
32
     */
33
    const OPENSSL_CIPHER_METHOD_OPTION_NAME = 'ee_openssl_cipher_method';
34
35
    /**
36
     * the OPENSSL digest method used
37
     */
38
    const OPENSSL_DIGEST_METHOD = 'sha512';
39
40
    /**
41
     * separates the encrypted text from the initialization vector
42
     */
43
    const OPENSSL_IV_DELIMITER = ':iv:';
44
45
    /**
46
     * appended to text encrypted using the acme encryption
47
     */
48
    const ACME_ENCRYPTION_FLAG = '::ae';
49
50
51
52
    /**
53
     * instance of the EE_Encryption object
54
     */
55
    protected static $_instance;
56
57
    /**
58
     * @var string $_encryption_key
59
     */
60
    protected $_encryption_key;
61
62
    /**
63
     * @var string $cipher_method
64
     */
65
    private $cipher_method = '';
66
67
    /**
68
     * @var array $cipher_methods
69
     */
70
    private $cipher_methods = array();
71
72
    /**
73
     * @var array $digest_methods
74
     */
75
    private $digest_methods = array();
76
77
    /**
78
     * @var boolean $_use_openssl_encrypt
79
     */
80
    protected $_use_openssl_encrypt = false;
81
82
    /**
83
     * @var boolean $_use_mcrypt
84
     */
85
    protected $_use_mcrypt = false;
86
87
    /**
88
     * @var boolean $_use_base64_encode
89
     */
90
    protected $_use_base64_encode = false;
91
92
93
94
    /**
95
     * protected constructor to prevent direct creation
96
     */
97
    protected function __construct()
98
    {
99
        if (! defined('ESPRESSO_ENCRYPT')) {
100
            define('ESPRESSO_ENCRYPT', true);
101
        }
102
        if (extension_loaded('openssl')) {
103
            $this->_use_openssl_encrypt = true;
104
        } else if (extension_loaded('mcrypt')) {
105
            $this->_use_mcrypt = true;
106
        }
107
        if (function_exists('base64_encode')) {
108
            $this->_use_base64_encode = true;
109
        }
110
    }
111
112
113
114
    /**
115
     * singleton method used to instantiate class object
116
     *
117
     * @return EE_Encryption
118
     */
119
    public static function instance()
120
    {
121
        // check if class object is instantiated
122
        if (! self::$_instance instanceof EE_Encryption) {
123
            self::$_instance = new self();
124
        }
125
        return self::$_instance;
126
    }
127
128
129
130
    /**
131
     * get encryption key
132
     *
133
     * @return string
134
     */
135
    public function get_encryption_key()
136
    {
137
        // if encryption key has not been set
138
        if (empty($this->_encryption_key)) {
139
            // retrieve encryption_key from db
140
            $this->_encryption_key = get_option(EE_Encryption::ENCRYPTION_OPTION_KEY, '');
141
            // WHAT?? No encryption_key in the db ??
142
            if ($this->_encryption_key === '') {
143
                // let's make one. And md5 it to make it just the right size for a key
144
                $new_key = md5($this->generate_random_string());
145
                // now save it to the db for later
146
                add_option(EE_Encryption::ENCRYPTION_OPTION_KEY, $new_key);
147
                // here's the key - FINALLY !
148
                $this->_encryption_key = $new_key;
149
            }
150
        }
151
        return $this->_encryption_key;
152
    }
153
154
155
156
    /**
157
     * encrypts data
158
     *
159
     * @param string $text_string - the text to be encrypted
160
     * @return string
161
     * @throws RuntimeException
162
     */
163
    public function encrypt($text_string = '')
164
    {
165
        // you give me nothing??? GET OUT !
166
        if (empty($text_string)) {
167
            return $text_string;
168
        }
169
        if ($this->_use_openssl_encrypt) {
170
            $encrypted_text = $this->openssl_encrypt($text_string);
171
        } else {
172
            $encrypted_text = $this->acme_encrypt($text_string);
173
        }
174
        return $encrypted_text;
175
    }
176
177
178
179
    /**
180
     * decrypts data
181
     *
182
     * @param string $encrypted_text - the text to be decrypted
183
     * @return string
184
     * @throws RuntimeException
185
     */
186
    public function decrypt($encrypted_text = '')
187
    {
188
        // you give me nothing??? GET OUT !
189
        if (empty($encrypted_text)) {
190
            return $encrypted_text;
191
        }
192
        // if PHP's mcrypt functions are installed then we'll use them
193
        if ($this->_use_openssl_encrypt) {
194
            $decrypted_text = $this->openssl_decrypt($encrypted_text);
195
        } else {
196
            $decrypted_text = $this->acme_decrypt($encrypted_text);
197
        }
198
        return $decrypted_text;
199
    }
200
201
202
203
    /**
204
     * encodes string with PHP's base64 encoding
205
     *
206
     * @see http://php.net/manual/en/function.base64-encode.php
207
     * @param string $text_string the text to be encoded
208
     * @return string
209
     */
210
    public function base64_string_encode($text_string = '')
211
    {
212
        // you give me nothing??? GET OUT !
213
        if (empty($text_string) || ! $this->_use_base64_encode) {
214
            return $text_string;
215
        }
216
        // encode
217
        return base64_encode($text_string);
218
    }
219
220
221
222
    /**
223
     * decodes string that has been encoded with PHP's base64 encoding
224
     *
225
     * @see http://php.net/manual/en/function.base64-encode.php
226
     * @param string $encoded_string the text to be decoded
227
     * @return string
228
     */
229
    public function base64_string_decode($encoded_string = '')
230
    {
231
        // you give me nothing??? GET OUT !
232
        if (empty($encoded_string) || ! $this->valid_base_64($encoded_string)) {
233
            return $encoded_string;
234
        }
235
        // decode
236
        return base64_decode($encoded_string);
237
    }
238
239
240
241
    /**
242
     * encodes  url string with PHP's base64 encoding
243
     *
244
     * @see http://php.net/manual/en/function.base64-encode.php
245
     * @param string $text_string the text to be encoded
246
     * @return string
247
     */
248
    public function base64_url_encode($text_string = '')
249
    {
250
        // you give me nothing??? GET OUT !
251
        if (empty($text_string) || ! $this->_use_base64_encode) {
252
            return $text_string;
253
        }
254
        // encode
255
        $encoded_string = base64_encode($text_string);
256
        // remove chars to make encoding more URL friendly
257
        return strtr($encoded_string, '+/=', '-_,');
258
    }
259
260
261
262
    /**
263
     * decodes  url string that has been encoded with PHP's base64 encoding
264
     *
265
     * @see http://php.net/manual/en/function.base64-encode.php
266
     * @param string $encoded_string the text to be decoded
267
     * @return string
268
     */
269
    public function base64_url_decode($encoded_string = '')
270
    {
271
        // you give me nothing??? GET OUT !
272
        if (empty($encoded_string) || ! $this->valid_base_64($encoded_string)) {
273
            return $encoded_string;
274
        }
275
        // replace previously removed characters
276
        $encoded_string = strtr($encoded_string, '-_,', '+/=');
277
        // decode
278
        return base64_decode($encoded_string);
279
    }
280
281
282
283
    /**
284
     * encrypts data using PHP's openssl functions
285
     *
286
     * @param string $text_string the text to be encrypted
287
     * @return string
288
     * @throws RuntimeException
289
     */
290
    protected function openssl_encrypt($text_string = '')
291
    {
292
        // you give me nothing??? GET OUT !
293
        if (empty($text_string)) {
294
            return $text_string;
295
        }
296
        $this->cipher_method = $this->getCipherMethod();
297
        // get initialization vector size
298
        $iv_size = openssl_cipher_iv_length($this->cipher_method);
299
        // generate initialization vector.
300
        // The second parameter ("crypto_strong") is passed by reference,
301
        // and is used to determines if the algorithm used was "cryptographically strong"
302
        // openssl_random_pseudo_bytes() will toggle it to either true or false
303
        $iv = openssl_random_pseudo_bytes($iv_size, $is_strong);
304
        if ($iv === false || $is_strong === false) {
305
            throw new RuntimeException(
306
                esc_html__('Failed to generate OpenSSL initialization vector.', 'event_espresso')
307
            );
308
        }
309
        // encrypt it
310
        $encrypted_text = openssl_encrypt(
311
            $text_string,
312
            $this->cipher_method,
313
            $this->getDigestHashValue(),
314
            0,
315
            $iv
316
        );
317
        // append the initialization vector
318
        $encrypted_text .= EE_Encryption::OPENSSL_IV_DELIMITER . $iv;
319
        // trim and maybe encode
320
        return $this->_use_base64_encode
321
            ? trim(base64_encode($encrypted_text))
322
            : trim($encrypted_text);
323
    }
324
325
326
327
    /**
328
     * Returns a cipher method that has been verified to work.
329
     * First checks if the cached cipher has been set already and if so, returns that.
330
     * Then tests the incoming default and returns that if it's good.
331
     * If not, then it retrieves the previously tested and saved cipher method.
332
     * But if that doesn't exist, then calls getAvailableCipherMethod()
333
     * to see what is available on the server, and returns the results.
334
     *
335
     * @param string $cipher_method
336
     * @return string
337
     * @throws RuntimeException
338
     */
339
    protected function getCipherMethod($cipher_method = EE_Encryption::OPENSSL_CIPHER_METHOD)
340
    {
341
        if($this->cipher_method !== ''){
342
            return $this->cipher_method;
343
        }
344
        // verify that the default cipher method can produce an initialization vector
345
        if (openssl_cipher_iv_length($cipher_method) === false) {
346
            // nope? okay let's get what we found in the past to work
347
            $cipher_method = get_option(EE_Encryption::OPENSSL_CIPHER_METHOD_OPTION_NAME, '');
348
            // oops... haven't tested available cipher methods yet
349
            if($cipher_method === '' || openssl_cipher_iv_length($cipher_method) === false) {
350
                $cipher_method = $this->getAvailableCipherMethod($cipher_method);
351
            }
352
        }
353
        return $cipher_method;
354
    }
355
356
357
358
    /**
359
     * @param string $cipher_method
360
     * @return string
361
     * @throws \RuntimeException
362
     */
363
    protected function getAvailableCipherMethod($cipher_method)
364
    {
365
        // verify that the incoming cipher method can produce an initialization vector
366
        if (openssl_cipher_iv_length($cipher_method) === false) {
367
            // nope? then check the next cipher in the list of available cipher methods
368
            $cipher_method = next($this->cipher_methods);
369
            // what? there's no list? then generate that list and cache it,
370
            if (empty($this->cipher_methods)) {
371
                $this->cipher_methods = openssl_get_cipher_methods();
372
                // then grab the first item from the list
373
                $cipher_method = reset($this->cipher_methods);
374
            }
375
            if($cipher_method === false){
376
                throw new RuntimeException(
377
                    esc_html__(
378
                        'OpenSSL support appears to be enabled on the server, but no cipher methods are available. Please contact the server administrator.',
379
                        'event_espresso'
380
                    )
381
                );
382
            }
383
            // verify that the next cipher method works
384
            return $this->getAvailableCipherMethod($cipher_method);
385
        }
386
        // if we've gotten this far, then we found an available cipher method that works
387
        // so save that for next time
388
        update_option(
389
            EE_Encryption::OPENSSL_CIPHER_METHOD_OPTION_NAME,
390
            $cipher_method
391
        );
392
        return $cipher_method;
393
    }
394
395
396
397
    /**
398
     * decrypts data that has been encrypted with PHP's openssl functions
399
     *
400
     * @param string $encrypted_text the text to be decrypted
401
     * @return string
402
     * @throws RuntimeException
403
     */
404
    protected function openssl_decrypt($encrypted_text = '')
405
    {
406
        // you give me nothing??? GET OUT !
407
        if (empty($encrypted_text)) {
408
            return $encrypted_text;
409
        }
410
        // decode
411
        $encrypted_text = $this->valid_base_64($encrypted_text)
412
            ? base64_decode($encrypted_text)
413
            : $encrypted_text;
414
        $encrypted_components = explode(
415
            EE_Encryption::OPENSSL_IV_DELIMITER,
416
            $encrypted_text,
417
            2
418
        );
419
        // check that iv exists, and if not, maybe text was encoded using mcrypt?
420
        if ($this->_use_mcrypt && ! isset($encrypted_components[1])) {
421
            return $this->m_decrypt($encrypted_text);
0 ignored issues
show
Deprecated Code introduced by
The method EE_Encryption::m_decrypt() has been deprecated with message: 4.9.39

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
422
        }
423
        // decrypt it
424
        $decrypted_text = openssl_decrypt(
425
            $encrypted_components[0],
426
            $this->getCipherMethod(),
427
            $this->getDigestHashValue(),
428
            0,
429
            $encrypted_components[1]
430
        );
431
        $decrypted_text = trim($decrypted_text);
432
        return $decrypted_text;
433
    }
434
435
436
437
    /**
438
     * Computes the digest hash value using the specified digest method.
439
     * If that digest method fails to produce a valid hash value,
440
     * then we'll grab the next digest method and recursively try again until something works.
441
     *
442
     * @param string $digest_method
443
     * @return string
444
     * @throws RuntimeException
445
     */
446
    protected function getDigestHashValue($digest_method = EE_Encryption::OPENSSL_DIGEST_METHOD){
447
        $digest_hash_value = openssl_digest($this->get_encryption_key(), $digest_method);
448
        if ($digest_hash_value === false) {
449
            return $this->getDigestHashValue($this->getDigestMethod());
450
        }
451
        return $digest_hash_value;
452
    }
453
454
455
456
    /**
457
     * Returns the NEXT element in the $digest_methods array.
458
     * If the $digest_methods array is empty, then we populate it
459
     * with the available values returned from openssl_get_md_methods().
460
     *
461
     * @return string
462
     * @throws \RuntimeException
463
     */
464
    protected function getDigestMethod(){
465
        $digest_method = prev($this->digest_methods);
466
        if (empty($this->digest_methods)) {
467
            $this->digest_methods = openssl_get_md_methods();
468
            $digest_method = end($this->digest_methods);
469
        }
470
        if ($digest_method === false) {
471
            throw new RuntimeException(
472
                esc_html__(
473
                    'OpenSSL support appears to be enabled on the server, but no digest methods are available. Please contact the server administrator.',
474
                    'event_espresso'
475
                )
476
            );
477
        }
478
        return $digest_method;
479
    }
480
481
482
    /**
483
     * encrypts data for acme servers that didn't bother to install PHP mcrypt
484
     *
485
     * @see http://stackoverflow.com/questions/800922/how-to-encrypt-string-without-mcrypt-library-in-php
486
     * @param string $text_string the text to be decrypted
487
     * @return string
488
     */
489
    protected function acme_encrypt($text_string = '')
490
    {
491
        // you give me nothing??? GET OUT !
492
        if (empty($text_string)) {
493
            return $text_string;
494
        }
495
        $key_bits = str_split(
496
            str_pad(
497
                '',
498
                strlen($text_string),
499
                $this->get_encryption_key(),
500
                STR_PAD_RIGHT
501
            )
502
        );
503
        $string_bits = str_split($text_string);
504 View Code Duplication
        foreach ($string_bits as $k => $v) {
505
            $temp = ord($v) + ord($key_bits[$k]);
506
            $string_bits[$k] = chr($temp > 255 ? ($temp - 256) : $temp);
507
        }
508
        $encrypted_text = implode('', $string_bits);
509
        $encrypted_text .= EE_Encryption::ACME_ENCRYPTION_FLAG;
510
        return $this->_use_base64_encode
511
            ? base64_encode($encrypted_text)
512
            : $encrypted_text;
513
    }
514
515
516
517
    /**
518
     * decrypts data for acme servers that didn't bother to install PHP mcrypt
519
     *
520
     * @see http://stackoverflow.com/questions/800922/how-to-encrypt-string-without-mcrypt-library-in-php
521
     * @param string $encrypted_text the text to be decrypted
522
     * @return string
523
     * @throws RuntimeException
524
     */
525
    protected function acme_decrypt($encrypted_text = '')
526
    {
527
        // you give me nothing??? GET OUT !
528
        if (empty($encrypted_text)) {
529
            return $encrypted_text;
530
        }
531
        // decode the data ?
532
        $encrypted_text = $this->valid_base_64($encrypted_text)
533
            ? base64_decode($encrypted_text)
534
            : $encrypted_text;
535
        if (
536
            $this->_use_mcrypt
537
            && strpos($encrypted_text, EE_Encryption::ACME_ENCRYPTION_FLAG) === false
538
        ){
539
            return $this->m_decrypt($encrypted_text);
0 ignored issues
show
Deprecated Code introduced by
The method EE_Encryption::m_decrypt() has been deprecated with message: 4.9.39

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
540
        }
541
        $encrypted_text = substr($encrypted_text, 0, -4);
542
        $key_bits = str_split(
543
            str_pad(
544
                '',
545
                strlen($encrypted_text),
546
                $this->get_encryption_key(),
547
                STR_PAD_RIGHT
548
            )
549
        );
550
        $string_bits = str_split($encrypted_text);
551 View Code Duplication
        foreach ($string_bits as $k => $v) {
552
            $temp = ord($v) - ord($key_bits[$k]);
553
            $string_bits[$k] = chr($temp < 0 ? ($temp + 256) : $temp);
554
        }
555
        return implode('', $string_bits);
556
    }
557
558
559
560
    /**
561
     * @see http://stackoverflow.com/questions/2556345/detect-base64-encoding-in-php#30231906
562
     * @param $string
563
     * @return bool
564
     */
565
    protected function valid_base_64($string)
566
    {
567
        // ensure data is a string
568
        if (! is_string($string) || ! $this->_use_base64_encode) {
569
            return false;
570
        }
571
        $decoded = base64_decode($string, true);
572
        // Check if there is no invalid character in string
573
        if (! preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string)) {
574
            return false;
575
        }
576
        // Decode the string in strict mode and send the response
577
        if (! base64_decode($string, true)) {
578
            return false;
579
        }
580
        // Encode and compare it to original one
581
        return base64_encode($decoded) === $string;
582
    }
583
584
585
586
    /**
587
     * generate random string
588
     *
589
     * @see http://stackoverflow.com/questions/637278/what-is-the-best-way-to-generate-a-random-key-within-php
590
     * @param int $length number of characters for random string
591
     * @return string
592
     */
593
    public function generate_random_string($length = 40)
594
    {
595
        $iterations = ceil($length / 40);
596
        $random_string = '';
597
        for ($i = 0; $i < $iterations; $i++) {
598
            $random_string .= sha1(microtime(true) . mt_rand(10000, 90000));
599
        }
600
        $random_string = substr($random_string, 0, $length);
601
        return $random_string;
602
    }
603
604
605
606
    /**
607
     * encrypts data using PHP's mcrypt functions
608
     *
609
     * @deprecated 4.9.39
610
     * @param string $text_string
611
     * @internal   param $string - the text to be encrypted
612
     * @return string
613
     * @throws RuntimeException
614
     */
615
    protected function m_encrypt($text_string = '')
616
    {
617
        // you give me nothing??? GET OUT !
618
        if (empty($text_string)) {
619
            return $text_string;
620
        }
621
        // get the initialization vector size
622
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
623
        // initialization vector
624
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
625
        if ($iv === false) {
626
            throw new RuntimeException(
627
                esc_html__('Failed to generate mcrypt initialization vector.', 'event_espresso')
628
            );
629
        }
630
        // encrypt it
631
        $encrypted_text = mcrypt_encrypt(
632
            MCRYPT_RIJNDAEL_256,
633
            $this->get_encryption_key(),
634
            $text_string,
635
            MCRYPT_MODE_ECB,
636
            $iv
637
        );
638
        // trim and maybe encode
639
        return $this->_use_base64_encode
640
            ? trim(base64_encode($encrypted_text))
641
            : trim($encrypted_text);
642
    }
643
644
645
646
    /**
647
     * decrypts data that has been encrypted with PHP's mcrypt functions
648
     *
649
     * @deprecated 4.9.39
650
     * @param string $encrypted_text the text to be decrypted
651
     * @return string
652
     * @throws RuntimeException
653
     */
654
    protected function m_decrypt($encrypted_text = '')
655
    {
656
        // you give me nothing??? GET OUT !
657
        if (empty($encrypted_text)) {
658
            return $encrypted_text;
659
        }
660
        // decode
661
        $encrypted_text = $this->valid_base_64($encrypted_text)
662
            ? base64_decode($encrypted_text)
663
            : $encrypted_text;
664
        // get the initialization vector size
665
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
666
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
667
        if ($iv === false) {
668
            throw new RuntimeException(
669
                esc_html__('Failed to generate mcrypt initialization vector.', 'event_espresso')
670
            );
671
        }
672
        // decrypt it
673
        $decrypted_text = mcrypt_decrypt(
674
            MCRYPT_RIJNDAEL_256,
675
            $this->get_encryption_key(),
676
            $encrypted_text,
677
            MCRYPT_MODE_ECB,
678
            $iv
679
        );
680
        $decrypted_text = trim($decrypted_text);
681
        return $decrypted_text;
682
    }
683
684
}
685
/* End of file EE_Encryption.class.php */
686
/* Location: /includes/core/EE_Encryption.core.php */
687