Passed
Push — master ( 157485...b7615a )
by Nikolaos
09:23
created

Crypt::encrypt()   C

Complexity

Conditions 11
Paths 34

Size

Total Lines 84
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
c 1
b 0
f 0
dl 0
loc 84
rs 6.8787
cc 11
nc 34
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Phalcon;
15
16
use Phalcon\Crypt\CryptInterface;
17
use Phalcon\Crypt\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Phalcon\Exception. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
18
use Phalcon\Crypt\MismatchException;
19
use Phalcon\Helper\Str;
20
21
use function base64_decode;
22
use function base64_encode;
23
use function chr;
24
use function function_exists;
25
use function hash;
26
use function hash_algos;
27
use function hash_hmac;
28
use function hash_hmac_algos;
29
use function in_array;
30
use function is_array;
31
use function openssl_cipher_iv_length;
32
use function openssl_decrypt;
33
use function openssl_encrypt;
34
use function openssl_get_cipher_methods;
35
use function openssl_random_pseudo_bytes;
36
use function ord;
37
use function rand;
38
use function range;
39
use function rtrim;
40
use function sprintf;
41
use function str_ireplace;
42
use function str_repeat;
43
use function strlen;
44
use function strrpos;
45
use function strtolower;
46
use function strtoupper;
47
use function substr;
48
49
use const OPENSSL_RAW_DATA;
50
51
/**
52
 * Provides encryption capabilities to Phalcon applications.
53
 *
54
 * ```php
55
 * use Phalcon\Crypt;
56
 *
57
 * $crypt = new Crypt();
58
 *
59
 * $crypt->setCipher('aes-256-ctr');
60
 *
61
 * $key  =
62
 * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3";
63
 * $text = "The message to be encrypted";
64
 *
65
 * $encrypted = $crypt->encrypt($text, $key);
66
 *
67
 * echo $crypt->decrypt($encrypted, $key);
68
 * ```
69
 *
70
 * @property string $authData
71
 * @property string $authTag
72
 * @property int    $authTagLength
73
 * @property array  $availableCiphers
74
 * @property string $cipher
75
 * @property string $hashAlgo
76
 * @property int    $ivLength
77
 * @property string $key
78
 * @property int    $padding
79
 * @property bool   $useSigning
80
 */
81
class Crypt implements CryptInterface
82
{
83
    public const PADDING_ANSI_X_923     = 1;
84
    public const PADDING_DEFAULT        = 0;
85
    public const PADDING_ISO_10126      = 3;
86
    public const PADDING_ISO_IEC_7816_4 = 4;
87
    public const PADDING_PKCS7          = 2;
88
    public const PADDING_SPACE          = 6;
89
    public const PADDING_ZERO           = 5;
90
91
    /**
92
     * @var string
93
     */
94
    protected $authData = "";
95
96
    /**
97
     * @var string
98
     */
99
    protected $authTag;
100
101
    /**
102
     * @var int
103
     */
104
    protected $authTagLength = 16;
105
106
    /**
107
     * Available cipher methods.
108
     *
109
     * @var array
110
     */
111
    protected $availableCiphers;
112
113
    /**
114
     * @var string
115
     */
116
    protected $cipher = "aes-256-cfb";
117
118
    /**
119
     * The name of hashing algorithm.
120
     *
121
     * @var string
122
     */
123
    protected $hashAlgo = "sha512";
124
125
    /**
126
     * The cipher iv length.
127
     *
128
     * @var int
129
     */
130
    protected $ivLength = 16;
131
132
    /**
133
     * @var string
134
     */
135
    protected $key = "";
136
137
    /**
138
     * @var int
139
     */
140
    protected $padding = 0;
141
142
    /**
143
     * Whether calculating message digest enabled or not.
144
     *
145
     * @var bool
146
     */
147
    protected $useSigning = true;
148
149
    /**
150
     * Crypt constructor.
151
     *
152
     * @param string $cipher
153
     * @param bool   $useSigning
154
     *
155
     * @throws Exception
156
     */
157
    public function __construct(
158
        string $cipher = "aes-256-cfb",
159
        bool $useSigning = false
160
    ) {
161
        $this->initializeAvailableCiphers();
162
163
        $this->setCipher($cipher);
164
        $this->useSigning($useSigning);
165
    }
166
167
    /**
168
     * Decrypts an encrypted text.
169
     *
170
     * ```php
171
     * $encrypted = $crypt->decrypt(
172
     *     $encrypted,
173
     *     "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
174
     * );
175
     * ```
176
     *
177
     * @param string      $text
178
     * @param string|null $key
179
     *
180
     * @return string
181
     * @throws Exception
182
     */
183
    public function decrypt(string $text, string $key = null): string
184
    {
185
        if (empty($key)) {
186
            $decryptKey = $this->key;
187
        } else {
188
            $decryptKey = $key;
189
        }
190
191
        if (empty($decryptKey)) {
192
            throw new Exception("Decryption key cannot be empty");
193
        }
194
195
        $cipher   = $this->cipher;
196
        $mode     = strtolower(
197
            substr($cipher, strrpos($cipher, "-") - strlen($cipher))
198
        );
199
        $authData = $this->authData;
200
        $authTag  = $this->authTag;
201
202
        $this->assertCipherIsAvailable($cipher);
203
204
        $ivLength = $this->ivLength;
205
206
        if ($ivLength > 0) {
207
            $blockSize = $ivLength;
208
        } else {
209
            $blockSize = $this->getIvLength(
210
                str_ireplace("-" . $mode, "", $cipher)
211
            );
212
        }
213
214
        $iv = mb_substr($text, 0, $ivLength, "8bit");
215
216
        if ($this->useSigning) {
217
            $hashAlgo   = $this->getHashAlgo();
218
            $hashLength = strlen(hash($hashAlgo, "", true));
219
            $hash       = mb_substr($text, $ivLength, $hashLength, "8bit");
220
            $ciphertext = mb_substr($text, $ivLength + $hashLength, null, "8bit");
221
222
            if (
223
                ("-gcm" === $mode || "-ccm" === $mode) &&
224
                !empty($this->authData)
225
            ) {
226
                $decrypted = openssl_decrypt(
227
                    $ciphertext,
228
                    $cipher,
229
                    $decryptKey,
230
                    OPENSSL_RAW_DATA,
231
                    $iv,
232
                    $authTag,
233
                    $authData
234
                );
235
            } else {
236
                $decrypted = openssl_decrypt(
237
                    $ciphertext,
238
                    $cipher,
239
                    $decryptKey,
240
                    OPENSSL_RAW_DATA,
241
                    $iv
242
                );
243
            }
244
245
            if ($mode == "-cbc" || $mode == "-ecb") {
246
                $decrypted = $this->cryptUnpadText(
247
                    $decrypted,
248
                    $mode,
249
                    $blockSize,
250
                    $this->padding
251
                );
252
            }
253
254
            /**
255
             * Checks on the decrypted's message digest using the HMAC method.
256
             */
257
            if (hash_hmac($hashAlgo, $decrypted, $decryptKey, true) !== $hash) {
258
                throw new MismatchException("Hash does not match.");
259
            }
260
261
            return $decrypted;
262
        }
263
264
        $ciphertext = mb_substr($text, $ivLength, null, "8bit");
265
266
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
267
            $decrypted = openssl_decrypt(
268
                $ciphertext,
269
                $cipher,
270
                $decryptKey,
271
                OPENSSL_RAW_DATA,
272
                $iv,
273
                $authTag,
274
                $authData
275
            );
276
        } else {
277
            $decrypted = openssl_decrypt(
278
                $ciphertext,
279
                $cipher,
280
                $decryptKey,
281
                OPENSSL_RAW_DATA,
282
                $iv
283
            );
284
        }
285
286
        if ($mode == "-cbc" || $mode == "-ecb") {
287
            $decrypted = $this->cryptUnpadText(
288
                $decrypted,
289
                $mode,
290
                $blockSize,
291
                $this->padding
292
            );
293
        }
294
295
        return $decrypted;
296
    }
297
298
    /**
299
     * Decrypt a text that is coded as a base64 string.
300
     *
301
     * @param string $text
302
     * @param null   $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
303
     * @param bool   $safe
304
     *
305
     * @return string
306
     * @throws Exception
307
     */
308
    public function decryptBase64(
309
        string $text,
310
        $key = null,
311
        bool $safe = false
312
    ): string {
313
        if ($safe) {
314
            $text = strtr($text, "-_", "+/") .
315
                substr("===", (strlen($text) + 3) % 4);
316
        }
317
318
        return $this->decrypt(
319
            base64_decode($text),
320
            $key
321
        );
322
    }
323
324
    /**
325
     * Encrypts a text.
326
     *
327
     * ```php
328
     * $encrypted = $crypt->encrypt(
329
     *     "Top secret",
330
     *     "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
331
     * );
332
     * ```
333
     *
334
     * @param string      $text
335
     * @param string|null $key
336
     *
337
     * @return string
338
     * @throws Exception
339
     */
340
    public function encrypt(string $text, string $key = null): string
341
    {
342
        if (empty($key)) {
343
            $encryptKey = $this->key;
344
        } else {
345
            $encryptKey = $key;
346
        }
347
348
        if (empty($encryptKey)) {
349
            throw new Exception("Encryption key cannot be empty");
350
        }
351
352
        $cipher = $this->cipher;
353
        $mode   = strtolower(
354
            substr(
355
                $cipher,
356
                strrpos($cipher, "-") - strlen($cipher)
357
            )
358
        );
359
360
        $this->assertCipherIsAvailable($cipher);
361
362
        $ivLength = $this->ivLength;
363
364
        if ($ivLength > 0) {
365
            $blockSize = $ivLength;
366
        } else {
367
            $blockSize = $this->getIvLength(
368
                str_ireplace(
369
                    "-" . $mode,
370
                    "",
371
                    $cipher
372
                )
373
            );
374
        }
375
376
        $iv          = openssl_random_pseudo_bytes($ivLength);
377
        $paddingType = $this->padding;
378
379
        if ($paddingType != 0 && ($mode == "-cbc" || $mode == "-ecb")) {
380
            $padded = $this->cryptPadText($text, $mode, $blockSize, $paddingType);
381
        } else {
382
            $padded = $text;
383
        }
384
385
        /**
386
         * If the mode is "gcm" or "ccm" and auth data has been passed call it
387
         * with that data
388
         */
389
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
390
            $authData      = $this->authData;
391
            $authTag       = $this->authTag;
392
            $authTagLength = $this->authTagLength;
393
394
            $encrypted = openssl_encrypt(
395
                $padded,
396
                $cipher,
397
                $encryptKey,
398
                OPENSSL_RAW_DATA,
399
                $iv,
400
                $authTag,
401
                $authData,
402
                $authTagLength
403
            );
404
405
            $this->authTag = $authTag;
406
        } else {
407
            $encrypted = openssl_encrypt(
408
                $padded,
409
                $cipher,
410
                $encryptKey,
411
                OPENSSL_RAW_DATA,
412
                $iv
413
            );
414
        }
415
416
        if ($this->useSigning) {
417
            $hashAlgo = $this->getHashAlgo();
418
            $digest   = hash_hmac($hashAlgo, $padded, $encryptKey, true);
419
420
            return $iv . $digest . $encrypted;
421
        }
422
423
        return $iv . $encrypted;
424
    }
425
426
    /**
427
     * Encrypts a text returning the result as a base64 string.
428
     *
429
     * @param string     $text
430
     * @param mixed|null $key
431
     * @param bool       $safe
432
     *
433
     * @return string
434
     * @throws Exception
435
     */
436
    public function encryptBase64(
437
        string $text,
438
        $key = null,
439
        bool $safe = false
440
    ): string {
441
        if ($safe == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
442
            return rtrim(
443
                strtr(
444
                    base64_encode(
445
                        $this->encrypt($text, $key)
446
                    ),
447
                    "+/",
448
                    "-_"
449
                ),
450
                "="
451
            );
452
        }
453
454
        return base64_encode(
455
            $this->encrypt($text, $key)
456
        );
457
    }
458
459
    /**
460
     * Returns a list of available ciphers.
461
     *
462
     * @return array
463
     * @throws Exception
464
     */
465
    public function getAvailableCiphers(): array
466
    {
467
        $availableCiphers = $this->availableCiphers;
468
469
        if (is_array($availableCiphers)) {
0 ignored issues
show
introduced by
The condition is_array($availableCiphers) is always true.
Loading history...
470
            $this->initializeAvailableCiphers();
471
472
            $availableCiphers = $this->availableCiphers;
473
        }
474
475
        $allowedCiphers = [];
476
        foreach ($availableCiphers as $cipher) {
477
            if (
478
                !(
479
                Str::endsWith(strtolower($cipher), "des") ||
480
                Str::endsWith(strtolower($cipher), "rc2") ||
481
                Str::endsWith(strtolower($cipher), "rc4") ||
482
                Str::endsWith(strtolower($cipher), "des") ||
483
                Str::endsWith(strtolower($cipher), "ecb")
484
                )
485
            ) {
486
                $allowedCiphers[] = $cipher;
487
            }
488
        }
489
490
        return $allowedCiphers;
491
    }
492
493
    /**
494
     * Return a list of registered hashing algorithms suitable for hash_hmac.
495
     */
496
    public function getAvailableHashAlgos(): array
497
    {
498
        if (function_exists("hash_hmac_algos")) {
499
            return hash_hmac_algos();
500
        }
501
502
        return hash_algos();
503
    }
504
505
    /**
506
     * @return string
507
     */
508
    public function getAuthData(): string
509
    {
510
        return $this->authData;
511
    }
512
513
    /**
514
     * @return string
515
     */
516
    public function getAuthTag(): string
517
    {
518
        return $this->authTag;
519
    }
520
521
    /**
522
     * @return int
523
     */
524
    public function getAuthTagLength(): int
525
    {
526
        return $this->authTagLength;
527
    }
528
529
    /**
530
     * Returns the current cipher
531
     */
532
    public function getCipher(): string
533
    {
534
        return $this->cipher;
535
    }
536
537
    /**
538
     * Get the name of hashing algorithm.
539
     */
540
    public function getHashAlgo(): string
541
    {
542
        return $this->hashAlgo;
543
    }
544
545
    /**
546
     * Returns the encryption key
547
     */
548
    public function getKey(): string
549
    {
550
        return $this->key;
551
    }
552
553
    /**
554
     * @param string $data
555
     *
556
     * @return CryptInterface
557
     */
558
    public function setAuthData(string $data): CryptInterface
559
    {
560
        $this->authData = $data;
561
562
        return $this;
563
    }
564
565
    /**
566
     * @param string $tag
567
     *
568
     * @return CryptInterface
569
     */
570
    public function setAuthTag(string $tag): CryptInterface
571
    {
572
        $this->authTag = $tag;
573
574
        return $this;
575
    }
576
577
    /**
578
     * @param int $length
579
     *
580
     * @return CryptInterface
581
     */
582
    public function setAuthTagLength(int $length): CryptInterface
583
    {
584
        $this->authTagLength = $length;
585
586
        return $this;
587
    }
588
589
    /**
590
     * Sets the cipher algorithm for data encryption and decryption.
591
     *
592
     * The `aes-256-gcm' is the preferable cipher, but it is not usable
593
     * until the openssl library is upgraded, which is available in PHP 7.1.
594
     *
595
     * The `aes-256-ctr' is arguably the best choice for cipher
596
     * algorithm for current openssl library version.
597
     *
598
     * @param string $cipher
599
     *
600
     * @return CryptInterface
601
     * @throws Exception
602
     */
603
    public function setCipher(string $cipher): CryptInterface
604
    {
605
        $this->assertCipherIsAvailable($cipher);
606
607
        $this->ivLength = $this->getIvLength($cipher);
608
        $this->cipher   = $cipher;
609
610
        return $this;
611
    }
612
613
    /**
614
     * Set the name of hashing algorithm.
615
     *
616
     * @param string $hashAlgo
617
     *
618
     * @return CryptInterface
619
     * @throws Exception
620
     */
621
    public function setHashAlgo(string $hashAlgo): CryptInterface
622
    {
623
        $this->assertHashAlgorithmAvailable($hashAlgo);
624
625
        $this->hashAlgo = $hashAlgo;
626
627
        return $this;
628
    }
629
630
    /**
631
     * Sets the encryption key.
632
     *
633
     * The `$key' should have been previously generated in a cryptographically
634
     * safe way.
635
     *
636
     * Bad key:
637
     * "le password"
638
     *
639
     * Better (but still unsafe):
640
     * "#1dj8$=dp?.ak//j1V$~%*0X"
641
     *
642
     * Good key:
643
     * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
644
     *
645
     * @param string $key
646
     *
647
     * @return CryptInterface
648
     */
649
    public function setKey(string $key): CryptInterface
650
    {
651
        $this->key = $key;
652
653
        return $this;
654
    }
655
656
    /**
657
     * Changes the padding scheme used.
658
     *
659
     * @param int $scheme
660
     *
661
     * @return CryptInterface
662
     */
663
    public function setPadding(int $scheme): CryptInterface
664
    {
665
        $this->padding = $scheme;
666
667
        return $this;
668
    }
669
670
    /**
671
     * Sets if the calculating message digest must used.
672
     *
673
     * @param bool $useSigning
674
     *
675
     * @return CryptInterface
676
     */
677
    public function useSigning(bool $useSigning): CryptInterface
678
    {
679
        $this->useSigning = $useSigning;
680
681
        return $this;
682
    }
683
684
    /**
685
     * Assert the cipher is available.
686
     *
687
     * @param string $cipher
688
     *
689
     * @throws Exception
690
     */
691
    protected function assertCipherIsAvailable(string $cipher): void
692
    {
693
        $availableCiphers = $this->getAvailableCiphers();
694
695
        if (!in_array(strtoupper($cipher), $availableCiphers)) {
696
            throw new Exception(
697
                sprintf(
698
                    "The cipher algorithm \"%s\" is not supported on this system.",
699
                    $cipher
700
                )
701
            );
702
        }
703
    }
704
705
    /**
706
     * Assert the hash algorithm is available.
707
     *
708
     * @param string $hashAlgo
709
     *
710
     * @throws Exception
711
     */
712
    protected function assertHashAlgorithmAvailable(string $hashAlgo): void
713
    {
714
        $availableAlgorithms = $this->getAvailableHashAlgos();
715
716
        if (!in_array($hashAlgo, $availableAlgorithms)) {
717
            throw new Exception(
718
                sprintf(
719
                    "The hash algorithm \"%s\" is not supported on this system.",
720
                    $hashAlgo
721
                )
722
            );
723
        }
724
    }
725
726
    /**
727
     * Pads texts before encryption.
728
     *
729
     * @see http://www.di-mgt.com.au/cryptopad.html
730
     *
731
     * @param string $text
732
     * @param string $mode
733
     * @param int    $blockSize
734
     * @param int    $paddingType
735
     *
736
     * @return string
737
     * @throws Exception
738
     */
739
    protected function cryptPadText(
740
        string $text,
741
        string $mode,
742
        int $blockSize,
743
        int $paddingType
744
    ): string {
745
746
        $paddingSize = 0;
747
        $padding     = null;
748
749
        if ($mode == "cbc" || $mode == "ecb") {
750
            $paddingSize = $blockSize - (strlen($text) % $blockSize);
751
752
            if ($paddingSize >= 256) {
753
                throw new Exception("Block size is bigger than 256");
754
            }
755
756
            switch ($paddingType) {
757
                case self::PADDING_ANSI_X_923:
758
                    $padding = str_repeat(chr(0), $paddingSize - 1) . chr($paddingSize);
759
                    break;
760
761
                case self::PADDING_PKCS7:
762
                    $padding = str_repeat(chr($paddingSize), $paddingSize);
763
                    break;
764
765
                case self::PADDING_ISO_10126:
766
                    $padding = "";
767
768
                    foreach (range(0, $paddingSize - 2) as $counter) {
769
                        $padding .= chr(rand());
770
                    }
771
772
                    $padding .= chr($paddingSize);
773
774
                    break;
775
776
                case self::PADDING_ISO_IEC_7816_4:
777
                    $padding = chr(0x80) . str_repeat(chr(0), $paddingSize - 1);
778
                    break;
779
780
                case self::PADDING_ZERO:
781
                    $padding = str_repeat(chr(0), $paddingSize);
782
                    break;
783
784
                case self::PADDING_SPACE:
785
                    $padding = str_repeat(" ", $paddingSize);
786
                    break;
787
788
                default:
789
                    $paddingSize = 0;
790
                    break;
791
            }
792
        }
793
794
        if (!$paddingSize) {
795
            return $text;
796
        }
797
798
        if ($paddingSize > $blockSize) {
799
            throw new Exception("Invalid padding size");
800
        }
801
802
        return $text . substr($padding, 0, $paddingSize);
803
    }
804
805
    /**
806
     * Removes a padding from a text.
807
     *
808
     * If the function detects that the text was not padded, it will return it
809
     * unmodified.
810
     *
811
     * @param string $text
812
     * @param string $mode
813
     * @param int    $blockSize
814
     * @param int    $paddingType
815
     *
816
     * @return false|string
817
     */
818
    protected function cryptUnpadText(
819
        string $text,
820
        string $mode,
821
        int $blockSize,
822
        int $paddingType
823
    ) {
824
        $paddingSize = 0;
825
        $length      = strlen($text);
826
827
        if (
828
            $length > 0 &&
829
            ($length % $blockSize == 0) &&
830
            ($mode == "cbc" || $mode == "ecb")
831
        ) {
832
            switch ($paddingType) {
833
                case self::PADDING_ANSI_X_923:
834
                    $last = substr($text, $length - 1, 1);
835
                    $ord  = (int) ord($last);
836
837
                    if ($ord <= $blockSize) {
838
                        $paddingSize = $ord;
839
                        $padding     = str_repeat(chr(0), $paddingSize - 1) . $last;
840
841
                        if (substr($text, $length - $paddingSize) != $padding) {
842
                            $paddingSize = 0;
843
                        }
844
                    }
845
846
                    break;
847
848
                case self::PADDING_PKCS7:
849
                    $last = substr($text, $length - 1, 1);
850
                    $ord  = (int) ord($last);
851
852
                    if ($ord <= $blockSize) {
853
                        $paddingSize = $ord;
854
                        $padding     = str_repeat(chr($paddingSize), $paddingSize);
855
856
                        if (substr($text, $length - $paddingSize) != $padding) {
857
                            $paddingSize = 0;
858
                        }
859
                    }
860
861
                    break;
862
863
                case self::PADDING_ISO_10126:
864
                    $last        = substr($text, $length - 1, 1);
865
                    $paddingSize = (int) ord($last);
866
                    break;
867
868
                case self::PADDING_ISO_IEC_7816_4:
869
                    $i = $length - 1;
870
871
                    while ($i > 0 && $text[$i] == 0x00 && $paddingSize < $blockSize) {
872
                        $paddingSize++;
873
                        $i--;
874
                    }
875
876
                    if ($text[$i] == 0x80) {
877
                        $paddingSize++;
878
                    } else {
879
                        $paddingSize = 0;
880
                    }
881
882
                    break;
883
884
                case self::PADDING_ZERO:
885
                    $i = $length - 1;
886
887
                    while ($i >= 0 && $text[$i] == 0x00 && $paddingSize <= $blockSize) {
888
                        $paddingSize++;
889
                        $i--;
890
                    }
891
892
                    break;
893
894
                case self::PADDING_SPACE:
895
                    $i = $length - 1;
896
897
                    while ($i >= 0 && $text[$i] == 0x20 && $paddingSize <= $blockSize) {
898
                        $paddingSize++;
899
                        $i--;
900
                    }
901
902
                    break;
903
904
                default:
905
                    break;
906
            }
907
908
            if ($paddingSize && $paddingSize <= $blockSize) {
909
                if ($paddingSize < $length) {
910
                    return substr($text, 0, $length - $paddingSize);
911
                }
912
913
                return "";
914
            } else {
915
                $paddingSize = 0;
916
            }
917
        }
918
919
        if (!$paddingSize) {
920
            return $text;
921
        }
922
    }
923
924
    /**
925
     * Initialize available cipher algorithms.
926
     *
927
     * @param string $cipher
928
     *
929
     * @return int
930
     * @throws Exception
931
     */
932
    protected function getIvLength(string $cipher): int
933
    {
934
        if (!function_exists("openssl_cipher_iv_length")) {
935
            throw new Exception("openssl extension is required");
936
        }
937
938
        return openssl_cipher_iv_length($cipher);
939
    }
940
941
    /**
942
     * Initialize available cipher algorithms.
943
     *
944
     * @throws Exception
945
     */
946
    protected function initializeAvailableCiphers(): void
947
    {
948
        if (!function_exists("openssl_get_cipher_methods")) {
949
            throw new Exception("openssl extension is required");
950
        }
951
952
        $availableCiphers = openssl_get_cipher_methods(true);
953
954
        foreach ($availableCiphers as $key => $cipher) {
955
            $availableCiphers[$key] = strtoupper($cipher);
956
        }
957
958
        $this->availableCiphers = $availableCiphers;
959
    }
960
}
961