Passed
Push — master ( de7816...ce3a1e )
by Nikolaos
07:17
created

Crypt::calculateWithSigning()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 56
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 31
c 1
b 0
f 0
dl 0
loc 56
rs 8.4906
cc 7
nc 8
nop 9

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 as CryptException;
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 openssl_cipher_iv_length;
31
use function openssl_decrypt;
32
use function openssl_encrypt;
33
use function openssl_get_cipher_methods;
34
use function openssl_random_pseudo_bytes;
35
use function ord;
36
use function rand;
37
use function range;
38
use function rtrim;
39
use function sprintf;
40
use function str_ireplace;
41
use function str_repeat;
42
use function strlen;
43
use function strrpos;
44
use function strtolower;
45
use function strtoupper;
46
use function substr;
47
48
use const OPENSSL_RAW_DATA;
49
50
/**
51
 * Provides encryption capabilities to Phalcon applications.
52
 *
53
 * ```php
54
 * use Phalcon\Crypt;
55
 *
56
 * $crypt = new Crypt();
57
 *
58
 * $crypt->setCipher('aes-256-ctr');
59
 *
60
 * $key  =
61
 * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3";
62
 * $text = "The message to be encrypted";
63
 *
64
 * $encrypted = $crypt->encrypt($text, $key);
65
 *
66
 * echo $crypt->decrypt($encrypted, $key);
67
 * ```
68
 *
69
 * @property string $authData
70
 * @property string $authTag
71
 * @property int    $authTagLength
72
 * @property array  $availableCiphers
73
 * @property string $cipher
74
 * @property string $hashAlgo
75
 * @property int    $ivLength
76
 * @property string $key
77
 * @property int    $padding
78
 * @property bool   $useSigning
79
 */
80
class Crypt implements CryptInterface
81
{
82
    public const PADDING_ANSI_X_923     = 1;
83
    public const PADDING_DEFAULT        = 0;
84
    public const PADDING_ISO_10126      = 3;
85
    public const PADDING_ISO_IEC_7816_4 = 4;
86
    public const PADDING_PKCS7          = 2;
87
    public const PADDING_SPACE          = 6;
88
    public const PADDING_ZERO           = 5;
89
90
    /**
91
     * @var string
92
     */
93
    protected $authData = "";
94
95
    /**
96
     * @var string
97
     */
98
    protected $authTag;
99
100
    /**
101
     * @var int
102
     */
103
    protected $authTagLength = 16;
104
105
    /**
106
     * Available cipher methods.
107
     *
108
     * @var array
109
     */
110
    protected $availableCiphers = [];
111
112
    /**
113
     * @var string
114
     */
115
    protected $cipher = "aes-256-cfb";
116
117
    /**
118
     * The name of hashing algorithm.
119
     *
120
     * @var string
121
     */
122
    protected $hashAlgo = "sha512";
123
124
    /**
125
     * The cipher iv length.
126
     *
127
     * @var int
128
     */
129
    protected $ivLength = 16;
130
131
    /**
132
     * @var string
133
     */
134
    protected $key = "";
135
136
    /**
137
     * @var int
138
     */
139
    protected $padding = 0;
140
141
    /**
142
     * Whether calculating message digest enabled or not.
143
     *
144
     * @var bool
145
     */
146
    protected $useSigning = true;
147
148
    /**
149
     * Crypt constructor.
150
     *
151
     * @param string $cipher
152
     * @param bool   $useSigning
153
     *
154
     * @throws CryptException
155
     */
156
    public function __construct(
157
        string $cipher = "aes-256-cfb",
158
        bool $useSigning = false
159
    ) {
160
        $this->initializeAvailableCiphers();
161
162
        $this->setCipher($cipher);
163
        $this->useSigning($useSigning);
164
    }
165
166
    /**
167
     * Decrypts an encrypted text.
168
     *
169
     * ```php
170
     * $encrypted = $crypt->decrypt(
171
     *     $encrypted,
172
     *     "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
173
     * );
174
     * ```
175
     *
176
     * @param string      $text
177
     * @param string|null $key
178
     *
179
     * @return string
180
     * @throws CryptException
181
     */
182
    public function decrypt(string $text, string $key = null): string
183
    {
184
        $decryptKey = $this->checkDecryptKey($key);
185
186
        $cipher   = $this->cipher;
187
        $mode     = strtolower(
188
            substr($cipher, strrpos($cipher, "-") - strlen($cipher))
189
        );
190
        $authData = $this->authData;
191
        $authTag  = $this->authTag;
192
193
        $this->assertCipherIsAvailable($cipher);
194
195
        $ivLength = $this->ivLength;
196
197
        if ($ivLength > 0) {
198
            $blockSize = $ivLength;
199
        } else {
200
            $blockSize = $this->getIvLength(
201
                str_ireplace("-" . $mode, "", $cipher)
202
            );
203
        }
204
205
        $iv = mb_substr($text, 0, $ivLength, "8bit");
206
207
        if ($this->useSigning) {
208
            return $this->calculateWithSigning(
209
                $text,
210
                $ivLength,
211
                $mode,
212
                $cipher,
213
                $decryptKey,
214
                $iv,
215
                $authTag,
216
                $authData,
217
                $blockSize
218
            );
219
        }
220
221
        $cipherText = mb_substr($text, $ivLength, null, "8bit");
222
        $decrypted  = $this->calculateGcmCcm(
223
            $mode,
224
            $cipherText,
225
            $cipher,
226
            $decryptKey,
227
            $iv,
228
            $authTag,
229
            $authData
230
        );
231
232
        if ($mode == "-cbc" || $mode == "-ecb") {
233
            $decrypted = $this->cryptUnpadText(
234
                $decrypted,
235
                $mode,
236
                $blockSize,
237
                $this->padding
238
            );
239
        }
240
241
        return $decrypted;
242
    }
243
244
    /**
245
     * Decrypt a text that is coded as a base64 string.
246
     *
247
     * @param string      $text
248
     * @param string|null $key
249
     * @param bool        $safe
250
     *
251
     * @return string
252
     * @throws CryptException
253
     */
254
    public function decryptBase64(
255
        string $text,
256
        $key = null,
257
        bool $safe = false
258
    ): string {
259
        if ($safe) {
260
            $text = strtr($text, "-_", "+/") .
261
                substr("===", (strlen($text) + 3) % 4);
262
        }
263
264
        return $this->decrypt(
265
            base64_decode($text),
266
            $key
267
        );
268
    }
269
270
    /**
271
     * Encrypts a text.
272
     *
273
     * ```php
274
     * $encrypted = $crypt->encrypt(
275
     *     "Top secret",
276
     *     "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
277
     * );
278
     * ```
279
     *
280
     * @param string      $text
281
     * @param string|null $key
282
     *
283
     * @return string
284
     * @throws CryptException
285
     */
286
    public function encrypt(string $text, string $key = null): string
287
    {
288
        if (empty($key)) {
289
            $encryptKey = $this->key;
290
        } else {
291
            $encryptKey = $key;
292
        }
293
294
        if (empty($encryptKey)) {
295
            throw new CryptException("Encryption key cannot be empty");
296
        }
297
298
        $cipher = $this->cipher;
299
        $mode   = strtolower(
300
            substr(
301
                $cipher,
302
                strrpos($cipher, "-") - strlen($cipher)
303
            )
304
        );
305
306
        $this->assertCipherIsAvailable($cipher);
307
308
        $ivLength = $this->ivLength;
309
310
        if ($ivLength > 0) {
311
            $blockSize = $ivLength;
312
        } else {
313
            $blockSize = $this->getIvLength(
314
                str_ireplace(
315
                    "-" . $mode,
316
                    "",
317
                    $cipher
318
                )
319
            );
320
        }
321
322
        $iv          = openssl_random_pseudo_bytes($ivLength);
323
        $paddingType = $this->padding;
324
325
        if ($paddingType != 0 && ($mode == "-cbc" || $mode == "-ecb")) {
326
            $padded = $this->cryptPadText($text, $mode, $blockSize, $paddingType);
327
        } else {
328
            $padded = $text;
329
        }
330
331
        /**
332
         * If the mode is "gcm" or "ccm" and auth data has been passed call it
333
         * with that data
334
         */
335
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
336
            $authData      = $this->authData;
337
            $authTag       = $this->authTag;
338
            $authTagLength = $this->authTagLength;
339
340
            $encrypted = openssl_encrypt(
341
                $padded,
342
                $cipher,
343
                $encryptKey,
344
                OPENSSL_RAW_DATA,
345
                $iv,
346
                $authTag,
347
                $authData,
348
                $authTagLength
349
            );
350
351
            $this->authTag = $authTag;
352
        } else {
353
            $encrypted = openssl_encrypt(
354
                $padded,
355
                $cipher,
356
                $encryptKey,
357
                OPENSSL_RAW_DATA,
358
                $iv
359
            );
360
        }
361
362
        if ($this->useSigning) {
363
            $hashAlgo = $this->getHashAlgo();
364
            $digest   = hash_hmac($hashAlgo, $padded, $encryptKey, true);
365
366
            return $iv . $digest . $encrypted;
367
        }
368
369
        return $iv . $encrypted;
370
    }
371
372
    /**
373
     * Encrypts a text returning the result as a base64 string.
374
     *
375
     * @param string     $text
376
     * @param mixed|null $key
377
     * @param bool       $safe
378
     *
379
     * @return string
380
     * @throws CryptException
381
     */
382
    public function encryptBase64(
383
        string $text,
384
        $key = null,
385
        bool $safe = false
386
    ): string {
387
        if ($safe === true) {
388
            return rtrim(
389
                strtr(
390
                    base64_encode(
391
                        $this->encrypt($text, $key)
392
                    ),
393
                    "+/",
394
                    "-_"
395
                ),
396
                "="
397
            );
398
        }
399
400
        return base64_encode(
401
            $this->encrypt($text, $key)
402
        );
403
    }
404
405
    /**
406
     * Returns a list of available ciphers.
407
     *
408
     * @return array
409
     * @throws CryptException
410
     */
411
    public function getAvailableCiphers(): array
412
    {
413
        $availableCiphers = $this->availableCiphers;
414
415
        if (empty($availableCiphers)) {
416
            $this->initializeAvailableCiphers();
417
418
            $availableCiphers = $this->availableCiphers;
419
        }
420
421
        $allowedCiphers = [];
422
        foreach ($availableCiphers as $cipher) {
423
            if (
424
                !Str::endsWith(strtolower($cipher), "des") &&
425
                !Str::endsWith(strtolower($cipher), "rc2") &&
426
                !Str::endsWith(strtolower($cipher), "rc4") &&
427
                !Str::endsWith(strtolower($cipher), "des") &&
428
                !Str::endsWith(strtolower($cipher), "ecb")
429
            ) {
430
                $allowedCiphers[] = $cipher;
431
            }
432
        }
433
434
        return $allowedCiphers;
435
    }
436
437
    /**
438
     * Return a list of registered hashing algorithms suitable for hash_hmac.
439
     */
440
    public function getAvailableHashAlgos(): array
441
    {
442
        if (function_exists("hash_hmac_algos")) {
443
            return hash_hmac_algos();
444
        }
445
446
        return hash_algos();
447
    }
448
449
    /**
450
     * @return string
451
     */
452
    public function getAuthData(): string
453
    {
454
        return $this->authData;
455
    }
456
457
    /**
458
     * @return string
459
     */
460
    public function getAuthTag(): string
461
    {
462
        return $this->authTag;
463
    }
464
465
    /**
466
     * @return int
467
     */
468
    public function getAuthTagLength(): int
469
    {
470
        return $this->authTagLength;
471
    }
472
473
    /**
474
     * Returns the current cipher
475
     */
476
    public function getCipher(): string
477
    {
478
        return $this->cipher;
479
    }
480
481
    /**
482
     * Get the name of hashing algorithm.
483
     */
484
    public function getHashAlgo(): string
485
    {
486
        return $this->hashAlgo;
487
    }
488
489
    /**
490
     * Returns the encryption key
491
     */
492
    public function getKey(): string
493
    {
494
        return $this->key;
495
    }
496
497
    /**
498
     * @param string $data
499
     *
500
     * @return CryptInterface
501
     */
502
    public function setAuthData(string $data): CryptInterface
503
    {
504
        $this->authData = $data;
505
506
        return $this;
507
    }
508
509
    /**
510
     * @param string $tag
511
     *
512
     * @return CryptInterface
513
     */
514
    public function setAuthTag(string $tag): CryptInterface
515
    {
516
        $this->authTag = $tag;
517
518
        return $this;
519
    }
520
521
    /**
522
     * @param int $length
523
     *
524
     * @return CryptInterface
525
     */
526
    public function setAuthTagLength(int $length): CryptInterface
527
    {
528
        $this->authTagLength = $length;
529
530
        return $this;
531
    }
532
533
    /**
534
     * Sets the cipher algorithm for data encryption and decryption.
535
     *
536
     * The `aes-256-gcm' is the preferable cipher, but it is not usable
537
     * until the openssl library is upgraded, which is available in PHP 7.1.
538
     *
539
     * The `aes-256-ctr' is arguably the best choice for cipher
540
     * algorithm for current openssl library version.
541
     *
542
     * @param string $cipher
543
     *
544
     * @return CryptInterface
545
     * @throws CryptException
546
     */
547
    public function setCipher(string $cipher): CryptInterface
548
    {
549
        $this->assertCipherIsAvailable($cipher);
550
551
        $this->ivLength = $this->getIvLength($cipher);
552
        $this->cipher   = $cipher;
553
554
        return $this;
555
    }
556
557
    /**
558
     * Set the name of hashing algorithm.
559
     *
560
     * @param string $hashAlgo
561
     *
562
     * @return CryptInterface
563
     * @throws CryptException
564
     */
565
    public function setHashAlgo(string $hashAlgo): CryptInterface
566
    {
567
        $this->assertHashAlgorithmAvailable($hashAlgo);
568
569
        $this->hashAlgo = $hashAlgo;
570
571
        return $this;
572
    }
573
574
    /**
575
     * Sets the encryption key.
576
     *
577
     * The `$key' should have been previously generated in a cryptographically
578
     * safe way.
579
     *
580
     * Bad key:
581
     * "le password"
582
     *
583
     * Better (but still unsafe):
584
     * "#1dj8$=dp?.ak//j1V$~%*0X"
585
     *
586
     * Good key:
587
     * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
588
     *
589
     * @param string $key
590
     *
591
     * @return CryptInterface
592
     */
593
    public function setKey(string $key): CryptInterface
594
    {
595
        $this->key = $key;
596
597
        return $this;
598
    }
599
600
    /**
601
     * Changes the padding scheme used.
602
     *
603
     * @param int $scheme
604
     *
605
     * @return CryptInterface
606
     */
607
    public function setPadding(int $scheme): CryptInterface
608
    {
609
        $this->padding = $scheme;
610
611
        return $this;
612
    }
613
614
    /**
615
     * Sets if the calculating message digest must used.
616
     *
617
     * @param bool $useSigning
618
     *
619
     * @return CryptInterface
620
     */
621
    public function useSigning(bool $useSigning): CryptInterface
622
    {
623
        $this->useSigning = $useSigning;
624
625
        return $this;
626
    }
627
628
    /**
629
     * Assert the cipher is available.
630
     *
631
     * @param string $cipher
632
     *
633
     * @throws CryptException
634
     */
635
    protected function assertCipherIsAvailable(string $cipher): void
636
    {
637
        $availableCiphers = $this->getAvailableCiphers();
638
639
        if (!in_array(strtoupper($cipher), $availableCiphers)) {
640
            throw new CryptException(
641
                sprintf(
642
                    "The cipher algorithm \"%s\" is not supported on this system.",
643
                    $cipher
644
                )
645
            );
646
        }
647
    }
648
649
    /**
650
     * Assert the hash algorithm is available.
651
     *
652
     * @param string $hashAlgo
653
     *
654
     * @throws CryptException
655
     */
656
    protected function assertHashAlgorithmAvailable(string $hashAlgo): void
657
    {
658
        $availableAlgorithms = $this->getAvailableHashAlgos();
659
660
        if (!in_array($hashAlgo, $availableAlgorithms)) {
661
            throw new CryptException(
662
                sprintf(
663
                    "The hash algorithm \"%s\" is not supported on this system.",
664
                    $hashAlgo
665
                )
666
            );
667
        }
668
    }
669
670
    /**
671
     * Pads texts before encryption.
672
     *
673
     * @see http://www.di-mgt.com.au/cryptopad.html
674
     *
675
     * @param string $text
676
     * @param string $mode
677
     * @param int    $blockSize
678
     * @param int    $paddingType
679
     *
680
     * @return string
681
     * @throws CryptException
682
     */
683
    protected function cryptPadText(
684
        string $text,
685
        string $mode,
686
        int $blockSize,
687
        int $paddingType
688
    ): string {
689
690
        $paddingSize = 0;
691
        $padding     = null;
692
693
        if ($mode == "cbc" || $mode == "ecb") {
694
            $paddingSize = $blockSize - (strlen($text) % $blockSize);
695
696
            if ($paddingSize >= 256) {
697
                throw new CryptException("Block size is bigger than 256");
698
            }
699
700
            switch ($paddingType) {
701
                case self::PADDING_ANSI_X_923:
702
                    $padding = str_repeat(chr(0), $paddingSize - 1) . chr($paddingSize);
703
                    break;
704
705
                case self::PADDING_PKCS7:
706
                    $padding = str_repeat(chr($paddingSize), $paddingSize);
707
                    break;
708
709
                case self::PADDING_ISO_10126:
710
                    $padding = "";
711
712
                    foreach (range(0, $paddingSize - 2) as $counter) {
713
                        $padding .= chr(rand());
714
                    }
715
716
                    $padding .= chr($paddingSize);
717
718
                    break;
719
720
                case self::PADDING_ISO_IEC_7816_4:
721
                    $padding = chr(0x80) . str_repeat(chr(0), $paddingSize - 1);
722
                    break;
723
724
                case self::PADDING_ZERO:
725
                    $padding = str_repeat(chr(0), $paddingSize);
726
                    break;
727
728
                case self::PADDING_SPACE:
729
                    $padding = str_repeat(" ", $paddingSize);
730
                    break;
731
732
                default:
733
                    $paddingSize = 0;
734
                    break;
735
            }
736
        }
737
738
        if (!$paddingSize) {
739
            return $text;
740
        }
741
742
        if ($paddingSize > $blockSize) {
743
            throw new CryptException("Invalid padding size");
744
        }
745
746
        return $text . substr($padding, 0, $paddingSize);
747
    }
748
749
    /**
750
     * Removes a padding from a text.
751
     *
752
     * If the function detects that the text was not padded, it will return it
753
     * unmodified.
754
     *
755
     * @param string $text
756
     * @param string $mode
757
     * @param int    $blockSize
758
     * @param int    $paddingType
759
     *
760
     * @return false|string
761
     */
762
    protected function cryptUnpadText(
763
        string $text,
764
        string $mode,
765
        int $blockSize,
766
        int $paddingType
767
    ) {
768
        $paddingSize = 0;
769
        $length      = strlen($text);
770
771
        if (
772
            $length > 0 &&
773
            ($length % $blockSize == 0) &&
774
            ($mode == "cbc" || $mode == "ecb")
775
        ) {
776
            switch ($paddingType) {
777
                case self::PADDING_ANSI_X_923:
778
                    $paddingSize = $this->getPaddingSizeAnsiX923(
779
                        $text,
780
                        $length,
781
                        $blockSize
782
                    );
783
                    break;
784
785
                case self::PADDING_PKCS7:
786
                    $paddingSize = $this->getPaddingSizePkcs7(
787
                        $text,
788
                        $length,
789
                        $blockSize
790
                    );
791
                    break;
792
793
                case self::PADDING_ISO_10126:
794
                    return $this->getPaddingSizeIso10126($text, $length);
795
796
                case self::PADDING_ISO_IEC_7816_4:
797
                    return $this->getPaddingSizeIsoIec78164(
798
                        $text,
799
                        $length,
800
                        $blockSize
801
                    );
802
803
                case self::PADDING_ZERO:
804
                    return $this->getPaddingSizeZero(
805
                        $text,
806
                        $length,
807
                        $blockSize
808
                    );
809
810
                case self::PADDING_SPACE:
811
                    return $this->getPaddingSizeSpace(
812
                        $text,
813
                        $length,
814
                        $blockSize
815
                    );
816
817
                default:
818
                    break;
819
            }
820
821
            if ($paddingSize && $paddingSize <= $blockSize) {
822
                if ($paddingSize < $length) {
823
                    return substr($text, 0, $length - $paddingSize);
824
                }
825
826
                return "";
827
            } else {
828
                $paddingSize = 0;
829
            }
830
        }
831
832
        if (!$paddingSize) {
833
            return $text;
834
        }
835
    }
836
837
    /**
838
     * Initialize available cipher algorithms.
839
     *
840
     * @param string $cipher
841
     *
842
     * @return int
843
     * @throws CryptException
844
     */
845
    protected function getIvLength(string $cipher): int
846
    {
847
        if (!function_exists("openssl_cipher_iv_length")) {
848
            throw new CryptException("openssl extension is required");
849
        }
850
851
        return openssl_cipher_iv_length($cipher);
852
    }
853
854
    /**
855
     * Initialize available cipher algorithms.
856
     *
857
     * @throws CryptException
858
     */
859
    protected function initializeAvailableCiphers(): void
860
    {
861
        if (!function_exists("openssl_get_cipher_methods")) {
862
            throw new CryptException("openssl extension is required");
863
        }
864
865
        $availableCiphers = openssl_get_cipher_methods(true);
866
867
        foreach ($availableCiphers as $key => $cipher) {
868
            $availableCiphers[$key] = strtoupper($cipher);
869
        }
870
871
        $this->availableCiphers = $availableCiphers;
872
    }
873
874
    /**
875
     * @param string $mode
876
     * @param string $cipherText
877
     * @param string $cipher
878
     * @param string $decryptKey
879
     * @param string $iv
880
     * @param string $authTag
881
     * @param string $authData
882
     *
883
     * @return string
884
     */
885
    private function calculateGcmCcm(
886
        string $mode,
887
        string $cipherText,
888
        string $cipher,
889
        string $decryptKey,
890
        string $iv,
891
        string $authTag,
892
        string $authData
893
    ): string {
894
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
895
            $decrypted = openssl_decrypt(
896
                $cipherText,
897
                $cipher,
898
                $decryptKey,
899
                OPENSSL_RAW_DATA,
900
                $iv,
901
                $authTag,
902
                $authData
903
            );
904
        } else {
905
            $decrypted = openssl_decrypt(
906
                $cipherText,
907
                $cipher,
908
                $decryptKey,
909
                OPENSSL_RAW_DATA,
910
                $iv
911
            );
912
        }
913
914
        return $decrypted;
915
    }
916
917
    /**
918
     * @param string $text
919
     * @param int    $ivLength
920
     * @param string $mode
921
     * @param string $cipher
922
     * @param string $decryptKey
923
     * @param string $iv
924
     * @param string $authTag
925
     * @param string $authData
926
     * @param int    $blockSize
927
     *
928
     * @return string
929
     * @throws MismatchException
930
     */
931
    private function calculateWithSigning(
932
        string $text,
933
        int $ivLength,
934
        string $mode,
935
        string $cipher,
936
        string $decryptKey,
937
        string $iv,
938
        string $authTag,
939
        string $authData,
940
        int $blockSize
941
    ): string {
942
        $hashAlgo   = $this->getHashAlgo();
943
        $hashLength = strlen(hash($hashAlgo, "", true));
944
        $hash       = mb_substr($text, $ivLength, $hashLength, "8bit");
945
        $cipherText = mb_substr($text, $ivLength + $hashLength, null, "8bit");
946
947
        if (
948
            ("-gcm" === $mode || "-ccm" === $mode) &&
949
            !empty($this->authData)
950
        ) {
951
            $decrypted = openssl_decrypt(
952
                $cipherText,
953
                $cipher,
954
                $decryptKey,
955
                OPENSSL_RAW_DATA,
956
                $iv,
957
                $authTag,
958
                $authData
959
            );
960
        } else {
961
            $decrypted = openssl_decrypt(
962
                $cipherText,
963
                $cipher,
964
                $decryptKey,
965
                OPENSSL_RAW_DATA,
966
                $iv
967
            );
968
        }
969
970
        if ($mode == "-cbc" || $mode == "-ecb") {
971
            $decrypted = $this->cryptUnpadText(
972
                $decrypted,
973
                $mode,
974
                $blockSize,
975
                $this->padding
976
            );
977
        }
978
979
        /**
980
         * Checks on the decrypted's message digest using the HMAC method.
981
         */
982
        if (hash_hmac($hashAlgo, $decrypted, $decryptKey, true) !== $hash) {
983
            throw new MismatchException("Hash does not match.");
984
        }
985
986
        return $decrypted;
987
    }
988
989
990
    /**
991
     * @param string|null $key
992
     *
993
     * @return string
994
     * @throws CryptException
995
     */
996
    private function checkDecryptKey(string $key = null): string
997
    {
998
        if (empty($key)) {
999
            $decryptKey = $this->key;
1000
        } else {
1001
            $decryptKey = $key;
1002
        }
1003
1004
        if (empty($decryptKey)) {
1005
            throw new CryptException("Decryption key cannot be empty");
1006
        }
1007
1008
        return $decryptKey;
1009
    }
1010
1011
    /**
1012
     * @param string $text
1013
     * @param int    $length
1014
     * @param int    $blockSize
1015
     *
1016
     * @return int
1017
     */
1018
    private function getPaddingSizeAnsiX923(
1019
        string $text,
1020
        int $length,
1021
        int $blockSize
1022
    ): int {
1023
        $paddingSize = 0;
1024
        $last        = substr($text, $length - 1, 1);
1025
        $ord         = (int) ord($last);
1026
1027
        if ($ord <= $blockSize) {
1028
            $paddingSize = $ord;
1029
            $padding     = str_repeat(chr(0), $paddingSize - 1) . $last;
1030
1031
            if (substr($text, $length - $paddingSize) != $padding) {
1032
                $paddingSize = 0;
1033
            }
1034
        }
1035
1036
        return $paddingSize;
1037
    }
1038
1039
    /**
1040
     * @param string $text
1041
     * @param int    $length
1042
     * @param int    $blockSize
1043
     *
1044
     * @return int
1045
     */
1046
    private function getPaddingSizePkcs7(
1047
        string $text,
1048
        int $length,
1049
        int $blockSize
1050
    ): int {
1051
        $paddingSize = 0;
1052
        $last = substr($text, $length - 1, 1);
1053
        $ord  = (int) ord($last);
1054
1055
        if ($ord <= $blockSize) {
1056
            $paddingSize = $ord;
1057
            $padding     = str_repeat(chr($paddingSize), $paddingSize);
1058
1059
            if (substr($text, $length - $paddingSize) != $padding) {
1060
                $paddingSize = 0;
1061
            }
1062
        }
1063
1064
        return $paddingSize;
1065
    }
1066
1067
    /**
1068
     * @param string $text
1069
     * @param int    $length
1070
     *
1071
     * @return int
1072
     */
1073
    private function getPaddingSizeIso10126(
1074
        string $text,
1075
        int $length
1076
    ): int {
1077
        $last = substr($text, $length - 1, 1);
1078
1079
        return (int) ord($last);
1080
    }
1081
1082
    /**
1083
     * @param string $text
1084
     * @param int    $length
1085
     * @param int    $blockSize
1086
     *
1087
     * @return int
1088
     */
1089
    private function getPaddingSizeIsoIec78164(
1090
        string $text,
1091
        int $length,
1092
        int $blockSize
1093
    ): int {
1094
        $paddingSize = 0;
1095
        $i = $length - 1;
1096
1097
        while ($i > 0 && $text[$i] == 0x00 && $paddingSize < $blockSize) {
1098
            $paddingSize++;
1099
            $i--;
1100
        }
1101
1102
        if ($text[$i] == 0x80) {
1103
            $paddingSize++;
1104
        } else {
1105
            $paddingSize = 0;
1106
        }
1107
1108
        return $paddingSize;
1109
    }
1110
1111
    /**
1112
     * @param string $text
1113
     * @param int    $length
1114
     * @param int    $blockSize
1115
     *
1116
     * @return int
1117
     */
1118
    private function getPaddingSizeZero(
1119
        string $text,
1120
        int $length,
1121
        int $blockSize
1122
    ): int {
1123
        $paddingSize = 0;
1124
        $i = $length - 1;
1125
1126
        while ($i >= 0 && $text[$i] == 0x00 && $paddingSize <= $blockSize) {
1127
            $paddingSize++;
1128
            $i--;
1129
        }
1130
1131
        return $paddingSize;
1132
    }
1133
1134
1135
1136
    /**
1137
     * @param string $text
1138
     * @param int    $length
1139
     * @param int    $blockSize
1140
     *
1141
     * @return int
1142
     */
1143
    private function getPaddingSizeSpace(
1144
        string $text,
1145
        int $length,
1146
        int $blockSize
1147
    ): int {
1148
        $paddingSize = 0;
1149
        $i = $length - 1;
1150
1151
        while ($i >= 0 && $text[$i] == 0x20 && $paddingSize <= $blockSize) {
1152
            $paddingSize++;
1153
            $i--;
1154
        }
1155
1156
        return $paddingSize;
1157
    }
1158
}
1159