Passed
Push — master ( 0fee30...de7816 )
by Nikolaos
09:18
created

Crypt::getPaddingSizePkcs7()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 19
rs 9.9666
cc 3
nc 3
nop 3
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
        if (empty($key)) {
185
            $decryptKey = $this->key;
186
        } else {
187
            $decryptKey = $key;
188
        }
189
190
        if (empty($decryptKey)) {
191
            throw new CryptException("Decryption key cannot be empty");
192
        }
193
194
        $cipher   = $this->cipher;
195
        $mode     = strtolower(
196
            substr($cipher, strrpos($cipher, "-") - strlen($cipher))
197
        );
198
        $authData = $this->authData;
199
        $authTag  = $this->authTag;
200
201
        $this->assertCipherIsAvailable($cipher);
202
203
        $ivLength = $this->ivLength;
204
205
        if ($ivLength > 0) {
206
            $blockSize = $ivLength;
207
        } else {
208
            $blockSize = $this->getIvLength(
209
                str_ireplace("-" . $mode, "", $cipher)
210
            );
211
        }
212
213
        $iv = mb_substr($text, 0, $ivLength, "8bit");
214
215
        if ($this->useSigning) {
216
            $hashAlgo   = $this->getHashAlgo();
217
            $hashLength = strlen(hash($hashAlgo, "", true));
218
            $hash       = mb_substr($text, $ivLength, $hashLength, "8bit");
219
            $cipherText = mb_substr($text, $ivLength + $hashLength, null, "8bit");
220
221
            if (
222
                ("-gcm" === $mode || "-ccm" === $mode) &&
223
                !empty($this->authData)
224
            ) {
225
                $decrypted = openssl_decrypt(
226
                    $cipherText,
227
                    $cipher,
228
                    $decryptKey,
229
                    OPENSSL_RAW_DATA,
230
                    $iv,
231
                    $authTag,
232
                    $authData
233
                );
234
            } else {
235
                $decrypted = openssl_decrypt(
236
                    $cipherText,
237
                    $cipher,
238
                    $decryptKey,
239
                    OPENSSL_RAW_DATA,
240
                    $iv
241
                );
242
            }
243
244
            if ($mode == "-cbc" || $mode == "-ecb") {
245
                $decrypted = $this->cryptUnpadText(
246
                    $decrypted,
247
                    $mode,
248
                    $blockSize,
249
                    $this->padding
250
                );
251
            }
252
253
            /**
254
             * Checks on the decrypted's message digest using the HMAC method.
255
             */
256
            if (hash_hmac($hashAlgo, $decrypted, $decryptKey, true) !== $hash) {
257
                throw new MismatchException("Hash does not match.");
258
            }
259
260
            return $decrypted;
261
        }
262
263
        $cipherText = mb_substr($text, $ivLength, null, "8bit");
264
265
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
266
            $decrypted = openssl_decrypt(
267
                $cipherText,
268
                $cipher,
269
                $decryptKey,
270
                OPENSSL_RAW_DATA,
271
                $iv,
272
                $authTag,
273
                $authData
274
            );
275
        } else {
276
            $decrypted = openssl_decrypt(
277
                $cipherText,
278
                $cipher,
279
                $decryptKey,
280
                OPENSSL_RAW_DATA,
281
                $iv
282
            );
283
        }
284
285
        if ($mode == "-cbc" || $mode == "-ecb") {
286
            $decrypted = $this->cryptUnpadText(
287
                $decrypted,
288
                $mode,
289
                $blockSize,
290
                $this->padding
291
            );
292
        }
293
294
        return $decrypted;
295
    }
296
297
    /**
298
     * Decrypt a text that is coded as a base64 string.
299
     *
300
     * @param string      $text
301
     * @param string|null $key
302
     * @param bool        $safe
303
     *
304
     * @return string
305
     * @throws CryptException
306
     */
307
    public function decryptBase64(
308
        string $text,
309
        $key = null,
310
        bool $safe = false
311
    ): string {
312
        if ($safe) {
313
            $text = strtr($text, "-_", "+/") .
314
                substr("===", (strlen($text) + 3) % 4);
315
        }
316
317
        return $this->decrypt(
318
            base64_decode($text),
319
            $key
320
        );
321
    }
322
323
    /**
324
     * Encrypts a text.
325
     *
326
     * ```php
327
     * $encrypted = $crypt->encrypt(
328
     *     "Top secret",
329
     *     "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
330
     * );
331
     * ```
332
     *
333
     * @param string      $text
334
     * @param string|null $key
335
     *
336
     * @return string
337
     * @throws CryptException
338
     */
339
    public function encrypt(string $text, string $key = null): string
340
    {
341
        if (empty($key)) {
342
            $encryptKey = $this->key;
343
        } else {
344
            $encryptKey = $key;
345
        }
346
347
        if (empty($encryptKey)) {
348
            throw new CryptException("Encryption key cannot be empty");
349
        }
350
351
        $cipher = $this->cipher;
352
        $mode   = strtolower(
353
            substr(
354
                $cipher,
355
                strrpos($cipher, "-") - strlen($cipher)
356
            )
357
        );
358
359
        $this->assertCipherIsAvailable($cipher);
360
361
        $ivLength = $this->ivLength;
362
363
        if ($ivLength > 0) {
364
            $blockSize = $ivLength;
365
        } else {
366
            $blockSize = $this->getIvLength(
367
                str_ireplace(
368
                    "-" . $mode,
369
                    "",
370
                    $cipher
371
                )
372
            );
373
        }
374
375
        $iv          = openssl_random_pseudo_bytes($ivLength);
376
        $paddingType = $this->padding;
377
378
        if ($paddingType != 0 && ($mode == "-cbc" || $mode == "-ecb")) {
379
            $padded = $this->cryptPadText($text, $mode, $blockSize, $paddingType);
380
        } else {
381
            $padded = $text;
382
        }
383
384
        /**
385
         * If the mode is "gcm" or "ccm" and auth data has been passed call it
386
         * with that data
387
         */
388
        if (("-gcm" === $mode || "-ccm" === $mode) && !empty($this->authData)) {
389
            $authData      = $this->authData;
390
            $authTag       = $this->authTag;
391
            $authTagLength = $this->authTagLength;
392
393
            $encrypted = openssl_encrypt(
394
                $padded,
395
                $cipher,
396
                $encryptKey,
397
                OPENSSL_RAW_DATA,
398
                $iv,
399
                $authTag,
400
                $authData,
401
                $authTagLength
402
            );
403
404
            $this->authTag = $authTag;
405
        } else {
406
            $encrypted = openssl_encrypt(
407
                $padded,
408
                $cipher,
409
                $encryptKey,
410
                OPENSSL_RAW_DATA,
411
                $iv
412
            );
413
        }
414
415
        if ($this->useSigning) {
416
            $hashAlgo = $this->getHashAlgo();
417
            $digest   = hash_hmac($hashAlgo, $padded, $encryptKey, true);
418
419
            return $iv . $digest . $encrypted;
420
        }
421
422
        return $iv . $encrypted;
423
    }
424
425
    /**
426
     * Encrypts a text returning the result as a base64 string.
427
     *
428
     * @param string     $text
429
     * @param mixed|null $key
430
     * @param bool       $safe
431
     *
432
     * @return string
433
     * @throws CryptException
434
     */
435
    public function encryptBase64(
436
        string $text,
437
        $key = null,
438
        bool $safe = false
439
    ): string {
440
        if ($safe === true) {
441
            return rtrim(
442
                strtr(
443
                    base64_encode(
444
                        $this->encrypt($text, $key)
445
                    ),
446
                    "+/",
447
                    "-_"
448
                ),
449
                "="
450
            );
451
        }
452
453
        return base64_encode(
454
            $this->encrypt($text, $key)
455
        );
456
    }
457
458
    /**
459
     * Returns a list of available ciphers.
460
     *
461
     * @return array
462
     * @throws CryptException
463
     */
464
    public function getAvailableCiphers(): array
465
    {
466
        $availableCiphers = $this->availableCiphers;
467
468
        if (empty($availableCiphers)) {
469
            $this->initializeAvailableCiphers();
470
471
            $availableCiphers = $this->availableCiphers;
472
        }
473
474
        $allowedCiphers = [];
475
        foreach ($availableCiphers as $cipher) {
476
            if (
477
                !Str::endsWith(strtolower($cipher), "des") &&
478
                !Str::endsWith(strtolower($cipher), "rc2") &&
479
                !Str::endsWith(strtolower($cipher), "rc4") &&
480
                !Str::endsWith(strtolower($cipher), "des") &&
481
                !Str::endsWith(strtolower($cipher), "ecb")
482
            ) {
483
                $allowedCiphers[] = $cipher;
484
            }
485
        }
486
487
        return $allowedCiphers;
488
    }
489
490
    /**
491
     * Return a list of registered hashing algorithms suitable for hash_hmac.
492
     */
493
    public function getAvailableHashAlgos(): array
494
    {
495
        if (function_exists("hash_hmac_algos")) {
496
            return hash_hmac_algos();
497
        }
498
499
        return hash_algos();
500
    }
501
502
    /**
503
     * @return string
504
     */
505
    public function getAuthData(): string
506
    {
507
        return $this->authData;
508
    }
509
510
    /**
511
     * @return string
512
     */
513
    public function getAuthTag(): string
514
    {
515
        return $this->authTag;
516
    }
517
518
    /**
519
     * @return int
520
     */
521
    public function getAuthTagLength(): int
522
    {
523
        return $this->authTagLength;
524
    }
525
526
    /**
527
     * Returns the current cipher
528
     */
529
    public function getCipher(): string
530
    {
531
        return $this->cipher;
532
    }
533
534
    /**
535
     * Get the name of hashing algorithm.
536
     */
537
    public function getHashAlgo(): string
538
    {
539
        return $this->hashAlgo;
540
    }
541
542
    /**
543
     * Returns the encryption key
544
     */
545
    public function getKey(): string
546
    {
547
        return $this->key;
548
    }
549
550
    /**
551
     * @param string $data
552
     *
553
     * @return CryptInterface
554
     */
555
    public function setAuthData(string $data): CryptInterface
556
    {
557
        $this->authData = $data;
558
559
        return $this;
560
    }
561
562
    /**
563
     * @param string $tag
564
     *
565
     * @return CryptInterface
566
     */
567
    public function setAuthTag(string $tag): CryptInterface
568
    {
569
        $this->authTag = $tag;
570
571
        return $this;
572
    }
573
574
    /**
575
     * @param int $length
576
     *
577
     * @return CryptInterface
578
     */
579
    public function setAuthTagLength(int $length): CryptInterface
580
    {
581
        $this->authTagLength = $length;
582
583
        return $this;
584
    }
585
586
    /**
587
     * Sets the cipher algorithm for data encryption and decryption.
588
     *
589
     * The `aes-256-gcm' is the preferable cipher, but it is not usable
590
     * until the openssl library is upgraded, which is available in PHP 7.1.
591
     *
592
     * The `aes-256-ctr' is arguably the best choice for cipher
593
     * algorithm for current openssl library version.
594
     *
595
     * @param string $cipher
596
     *
597
     * @return CryptInterface
598
     * @throws CryptException
599
     */
600
    public function setCipher(string $cipher): CryptInterface
601
    {
602
        $this->assertCipherIsAvailable($cipher);
603
604
        $this->ivLength = $this->getIvLength($cipher);
605
        $this->cipher   = $cipher;
606
607
        return $this;
608
    }
609
610
    /**
611
     * Set the name of hashing algorithm.
612
     *
613
     * @param string $hashAlgo
614
     *
615
     * @return CryptInterface
616
     * @throws CryptException
617
     */
618
    public function setHashAlgo(string $hashAlgo): CryptInterface
619
    {
620
        $this->assertHashAlgorithmAvailable($hashAlgo);
621
622
        $this->hashAlgo = $hashAlgo;
623
624
        return $this;
625
    }
626
627
    /**
628
     * Sets the encryption key.
629
     *
630
     * The `$key' should have been previously generated in a cryptographically
631
     * safe way.
632
     *
633
     * Bad key:
634
     * "le password"
635
     *
636
     * Better (but still unsafe):
637
     * "#1dj8$=dp?.ak//j1V$~%*0X"
638
     *
639
     * Good key:
640
     * "T4\xb1\x8d\xa9\x98\x05\\\x8c\xbe\x1d\x07&[\x99\x18\xa4~Lc1\xbeW\xb3"
641
     *
642
     * @param string $key
643
     *
644
     * @return CryptInterface
645
     */
646
    public function setKey(string $key): CryptInterface
647
    {
648
        $this->key = $key;
649
650
        return $this;
651
    }
652
653
    /**
654
     * Changes the padding scheme used.
655
     *
656
     * @param int $scheme
657
     *
658
     * @return CryptInterface
659
     */
660
    public function setPadding(int $scheme): CryptInterface
661
    {
662
        $this->padding = $scheme;
663
664
        return $this;
665
    }
666
667
    /**
668
     * Sets if the calculating message digest must used.
669
     *
670
     * @param bool $useSigning
671
     *
672
     * @return CryptInterface
673
     */
674
    public function useSigning(bool $useSigning): CryptInterface
675
    {
676
        $this->useSigning = $useSigning;
677
678
        return $this;
679
    }
680
681
    /**
682
     * Assert the cipher is available.
683
     *
684
     * @param string $cipher
685
     *
686
     * @throws CryptException
687
     */
688
    protected function assertCipherIsAvailable(string $cipher): void
689
    {
690
        $availableCiphers = $this->getAvailableCiphers();
691
692
        if (!in_array(strtoupper($cipher), $availableCiphers)) {
693
            throw new CryptException(
694
                sprintf(
695
                    "The cipher algorithm \"%s\" is not supported on this system.",
696
                    $cipher
697
                )
698
            );
699
        }
700
    }
701
702
    /**
703
     * Assert the hash algorithm is available.
704
     *
705
     * @param string $hashAlgo
706
     *
707
     * @throws CryptException
708
     */
709
    protected function assertHashAlgorithmAvailable(string $hashAlgo): void
710
    {
711
        $availableAlgorithms = $this->getAvailableHashAlgos();
712
713
        if (!in_array($hashAlgo, $availableAlgorithms)) {
714
            throw new CryptException(
715
                sprintf(
716
                    "The hash algorithm \"%s\" is not supported on this system.",
717
                    $hashAlgo
718
                )
719
            );
720
        }
721
    }
722
723
    /**
724
     * Pads texts before encryption.
725
     *
726
     * @see http://www.di-mgt.com.au/cryptopad.html
727
     *
728
     * @param string $text
729
     * @param string $mode
730
     * @param int    $blockSize
731
     * @param int    $paddingType
732
     *
733
     * @return string
734
     * @throws CryptException
735
     */
736
    protected function cryptPadText(
737
        string $text,
738
        string $mode,
739
        int $blockSize,
740
        int $paddingType
741
    ): string {
742
743
        $paddingSize = 0;
744
        $padding     = null;
745
746
        if ($mode == "cbc" || $mode == "ecb") {
747
            $paddingSize = $blockSize - (strlen($text) % $blockSize);
748
749
            if ($paddingSize >= 256) {
750
                throw new CryptException("Block size is bigger than 256");
751
            }
752
753
            switch ($paddingType) {
754
                case self::PADDING_ANSI_X_923:
755
                    $padding = str_repeat(chr(0), $paddingSize - 1) . chr($paddingSize);
756
                    break;
757
758
                case self::PADDING_PKCS7:
759
                    $padding = str_repeat(chr($paddingSize), $paddingSize);
760
                    break;
761
762
                case self::PADDING_ISO_10126:
763
                    $padding = "";
764
765
                    foreach (range(0, $paddingSize - 2) as $counter) {
766
                        $padding .= chr(rand());
767
                    }
768
769
                    $padding .= chr($paddingSize);
770
771
                    break;
772
773
                case self::PADDING_ISO_IEC_7816_4:
774
                    $padding = chr(0x80) . str_repeat(chr(0), $paddingSize - 1);
775
                    break;
776
777
                case self::PADDING_ZERO:
778
                    $padding = str_repeat(chr(0), $paddingSize);
779
                    break;
780
781
                case self::PADDING_SPACE:
782
                    $padding = str_repeat(" ", $paddingSize);
783
                    break;
784
785
                default:
786
                    $paddingSize = 0;
787
                    break;
788
            }
789
        }
790
791
        if (!$paddingSize) {
792
            return $text;
793
        }
794
795
        if ($paddingSize > $blockSize) {
796
            throw new CryptException("Invalid padding size");
797
        }
798
799
        return $text . substr($padding, 0, $paddingSize);
800
    }
801
802
    /**
803
     * Removes a padding from a text.
804
     *
805
     * If the function detects that the text was not padded, it will return it
806
     * unmodified.
807
     *
808
     * @param string $text
809
     * @param string $mode
810
     * @param int    $blockSize
811
     * @param int    $paddingType
812
     *
813
     * @return false|string
814
     */
815
    protected function cryptUnpadText(
816
        string $text,
817
        string $mode,
818
        int $blockSize,
819
        int $paddingType
820
    ) {
821
        $paddingSize = 0;
822
        $length      = strlen($text);
823
824
        if (
825
            $length > 0 &&
826
            ($length % $blockSize == 0) &&
827
            ($mode == "cbc" || $mode == "ecb")
828
        ) {
829
            switch ($paddingType) {
830
                case self::PADDING_ANSI_X_923:
831
                    $paddingSize = $this->getPaddingSizeAnsiX923(
832
                        $text,
833
                        $length,
834
                        $blockSize
835
                    );
836
                    break;
837
838
                case self::PADDING_PKCS7:
839
                    $paddingSize = $this->getPaddingSizePkcs7(
840
                        $text,
841
                        $length,
842
                        $blockSize
843
                    );
844
                    break;
845
846
                case self::PADDING_ISO_10126:
847
                    return $this->getPaddingSizeIso10126($text, $length);
848
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
849
850
                case self::PADDING_ISO_IEC_7816_4:
851
                    return $this->getPaddingSizeIsoIec78164(
852
                        $text,
853
                        $length,
854
                        $blockSize
855
                    );
856
                    break;
857
858
                case self::PADDING_ZERO:
859
                    return $this->getPaddingSizeZero(
860
                        $text,
861
                        $length,
862
                        $blockSize
863
                    );
864
                    break;
865
866
                case self::PADDING_SPACE:
867
                    return $this->getPaddingSizeSpace(
868
                        $text,
869
                        $length,
870
                        $blockSize
871
                    );
872
                    break;
873
874
                default:
875
                    break;
876
            }
877
878
            if ($paddingSize && $paddingSize <= $blockSize) {
879
                if ($paddingSize < $length) {
880
                    return substr($text, 0, $length - $paddingSize);
881
                }
882
883
                return "";
884
            } else {
885
                $paddingSize = 0;
886
            }
887
        }
888
889
        if (!$paddingSize) {
890
            return $text;
891
        }
892
    }
893
894
    /**
895
     * Initialize available cipher algorithms.
896
     *
897
     * @param string $cipher
898
     *
899
     * @return int
900
     * @throws CryptException
901
     */
902
    protected function getIvLength(string $cipher): int
903
    {
904
        if (!function_exists("openssl_cipher_iv_length")) {
905
            throw new CryptException("openssl extension is required");
906
        }
907
908
        return openssl_cipher_iv_length($cipher);
909
    }
910
911
    /**
912
     * Initialize available cipher algorithms.
913
     *
914
     * @throws CryptException
915
     */
916
    protected function initializeAvailableCiphers(): void
917
    {
918
        if (!function_exists("openssl_get_cipher_methods")) {
919
            throw new CryptException("openssl extension is required");
920
        }
921
922
        $availableCiphers = openssl_get_cipher_methods(true);
923
924
        foreach ($availableCiphers as $key => $cipher) {
925
            $availableCiphers[$key] = strtoupper($cipher);
926
        }
927
928
        $this->availableCiphers = $availableCiphers;
929
    }
930
931
    /**
932
     * @param string $text
933
     * @param int    $length
934
     * @param int    $blockSize
935
     *
936
     * @return int
937
     */
938
    private function getPaddingSizeAnsiX923(
939
        string $text,
940
        int $length,
941
        int $blockSize
942
    ): int {
943
        $paddingSize = 0;
944
        $last        = substr($text, $length - 1, 1);
945
        $ord         = (int) ord($last);
946
947
        if ($ord <= $blockSize) {
948
            $paddingSize = $ord;
949
            $padding     = str_repeat(chr(0), $paddingSize - 1) . $last;
950
951
            if (substr($text, $length - $paddingSize) != $padding) {
952
                $paddingSize = 0;
953
            }
954
        }
955
956
        return $paddingSize;
957
    }
958
959
    /**
960
     * @param string $text
961
     * @param int    $length
962
     * @param int    $blockSize
963
     *
964
     * @return int
965
     */
966
    private function getPaddingSizePkcs7(
967
        string $text,
968
        int $length,
969
        int $blockSize
970
    ): int {
971
        $paddingSize = 0;
972
        $last = substr($text, $length - 1, 1);
973
        $ord  = (int) ord($last);
974
975
        if ($ord <= $blockSize) {
976
            $paddingSize = $ord;
977
            $padding     = str_repeat(chr($paddingSize), $paddingSize);
978
979
            if (substr($text, $length - $paddingSize) != $padding) {
980
                $paddingSize = 0;
981
            }
982
        }
983
984
        return $paddingSize;
985
    }
986
987
    /**
988
     * @param string $text
989
     * @param int    $length
990
     *
991
     * @return int
992
     */
993
    private function getPaddingSizeIso10126(
994
        string $text,
995
        int $length
996
    ): int {
997
        $last = substr($text, $length - 1, 1);
998
999
        return (int) ord($last);
1000
    }
1001
1002
    /**
1003
     * @param string $text
1004
     * @param int    $length
1005
     * @param int    $blockSize
1006
     *
1007
     * @return int
1008
     */
1009
    private function getPaddingSizeIsoIec78164(
1010
        string $text,
1011
        int $length,
1012
        int $blockSize
1013
    ): int {
1014
        $paddingSize = 0;
1015
        $i = $length - 1;
1016
1017
        while ($i > 0 && $text[$i] == 0x00 && $paddingSize < $blockSize) {
1018
            $paddingSize++;
1019
            $i--;
1020
        }
1021
1022
        if ($text[$i] == 0x80) {
1023
            $paddingSize++;
1024
        } else {
1025
            $paddingSize = 0;
1026
        }
1027
1028
        return $paddingSize;
1029
    }
1030
1031
    /**
1032
     * @param string $text
1033
     * @param int    $length
1034
     * @param int    $blockSize
1035
     *
1036
     * @return int
1037
     */
1038
    private function getPaddingSizeZero(
1039
        string $text,
1040
        int $length,
1041
        int $blockSize
1042
    ): int {
1043
        $paddingSize = 0;
1044
        $i = $length - 1;
1045
1046
        while ($i >= 0 && $text[$i] == 0x00 && $paddingSize <= $blockSize) {
1047
            $paddingSize++;
1048
            $i--;
1049
        }
1050
1051
        return $paddingSize;
1052
    }
1053
1054
1055
1056
    /**
1057
     * @param string $text
1058
     * @param int    $length
1059
     * @param int    $blockSize
1060
     *
1061
     * @return int
1062
     */
1063
    private function getPaddingSizeSpace(
1064
        string $text,
1065
        int $length,
1066
        int $blockSize
1067
    ): int {
1068
        $paddingSize = 0;
1069
        $i = $length - 1;
1070
1071
        while ($i >= 0 && $text[$i] == 0x20 && $paddingSize <= $blockSize) {
1072
            $paddingSize++;
1073
            $i--;
1074
        }
1075
1076
        return $paddingSize;
1077
    }
1078
}
1079