1 <?php
2 
3 /**
4  * Pure-PHP implementation of Rijndael.
5  *
6  * Uses mcrypt, if available/possible, and an internal implementation, otherwise.
7  *
8  * PHP version 5
9  *
10  * If {@link self::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits.  If
11  * {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
12  * {@link self::setKey() setKey()}.  ie. if the key is 128-bits, the key length will be 128-bits.  If it's
13  * 136-bits it'll be null-padded to 192-bits and 192 bits will be the key length until
14  * {@link self::setKey() setKey()} is called, again, at which point, it'll be recalculated.
15  *
16  * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length.  mcrypt, for example,
17  * does not.  AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
18  * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
19  * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224.  Indeed, 160 and 224
20  * are first defined as valid key / block lengths in
21  * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
22  * Extensions: Other block and Cipher Key lengths.
23  * Note: Use of 160/224-bit Keys must be explicitly set by setKeyLength(160) respectively setKeyLength(224).
24  *
25  * {@internal The variable names are the same as those in
26  * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
27  *
28  * Here's a short example of how to use this library:
29  * <code>
30  * <?php
31  *    include 'vendor/autoload.php';
32  *
33  *    $rijndael = new \phpseclib3\Crypt\Rijndael('ctr');
34  *
35  *    $rijndael->setKey('abcdefghijklmnop');
36  *
37  *    $size = 10 * 1024;
38  *    $plaintext = '';
39  *    for ($i = 0; $i < $size; $i++) {
40  *        $plaintext.= 'a';
41  *    }
42  *
43  *    echo $rijndael->decrypt($rijndael->encrypt($plaintext));
44  * ?>
45  * </code>
46  *
47  * @author    Jim Wigginton <terrafrost@php.net>
48  * @copyright 2008 Jim Wigginton
49  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
50  * @link      http://phpseclib.sourceforge.net
51  */
52 
53 namespace phpseclib3\Crypt;
54 
55 use phpseclib3\Common\Functions\Strings;
56 use phpseclib3\Crypt\Common\BlockCipher;
57 use phpseclib3\Exception\BadDecryptionException;
58 use phpseclib3\Exception\BadModeException;
59 use phpseclib3\Exception\InconsistentSetupException;
60 use phpseclib3\Exception\InsufficientSetupException;
61 
62 /**
63  * Pure-PHP implementation of Rijndael.
64  *
65  * @author  Jim Wigginton <terrafrost@php.net>
66  */
67 class Rijndael extends BlockCipher
68 {
69     /**
70      * The mcrypt specific name of the cipher
71      *
72      * Mcrypt is useable for 128/192/256-bit $block_size/$key_length. For 160/224 not.
73      * \phpseclib3\Crypt\Rijndael determines automatically whether mcrypt is useable
74      * or not for the current $block_size/$key_length.
75      * In case of, $cipher_name_mcrypt will be set dynamically at run time accordingly.
76      *
77      * @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt
78      * @see \phpseclib3\Crypt\Common\SymmetricKey::engine
79      * @see self::isValidEngine()
80      * @var string
81      */
82     protected $cipher_name_mcrypt = 'rijndael-128';
83 
84     /**
85      * The Key Schedule
86      *
87      * @see self::setup()
88      * @var array
89      */
90     private $w;
91 
92     /**
93      * The Inverse Key Schedule
94      *
95      * @see self::setup()
96      * @var array
97      */
98     private $dw;
99 
100     /**
101      * The Block Length divided by 32
102      *
103      * {@internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4.  Exists in conjunction with $block_size
104      *    because the encryption / decryption / key schedule creation requires this number and not $block_size.  We could
105      *    derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
106      *    of that, we'll just precompute it once.}
107      *
108      * @see self::setBlockLength()
109      * @var int
110      */
111     private $Nb = 4;
112 
113     /**
114      * The Key Length (in bytes)
115      *
116      * {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16.  Exists in conjunction with $Nk
117      *    because the encryption / decryption / key schedule creation requires this number and not $key_length.  We could
118      *    derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
119      *    of that, we'll just precompute it once.}
120      *
121      * @see self::setKeyLength()
122      * @var int
123      */
124     protected $key_length = 16;
125 
126     /**
127      * The Key Length divided by 32
128      *
129      * @see self::setKeyLength()
130      * @var int
131      * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
132      */
133     private $Nk = 4;
134 
135     /**
136      * The Number of Rounds
137      *
138      * {@internal The max value is 14, the min value is 10.}
139      *
140      * @var int
141      */
142     private $Nr;
143 
144     /**
145      * Shift offsets
146      *
147      * @var array
148      */
149     private $c;
150 
151     /**
152      * Holds the last used key- and block_size information
153      *
154      * @var array
155      */
156     private $kl;
157 
158     /**
159      * Default Constructor.
160      *
161      * @param string $mode
162      * @throws \InvalidArgumentException if an invalid / unsupported mode is provided
163      */
164     public function __construct($mode)
165     {
166         parent::__construct($mode);
167 
168         if ($this->mode == self::MODE_STREAM) {
169             throw new BadModeException('Block ciphers cannot be ran in stream mode');
170         }
171     }
172 
173     /**
174      * Sets the key length.
175      *
176      * Valid key lengths are 128, 160, 192, 224, and 256.
177      *
178      * Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined
179      *       and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to
180      *       192/256 bits as, for example, mcrypt will do.
181      *
182      *       That said, if you want be compatible with other Rijndael and AES implementations,
183      *       you should not setKeyLength(160) or setKeyLength(224).
184      *
185      * Additional: In case of 160- and 224-bit keys, phpseclib will/can, for that reason, not use
186      *             the mcrypt php extension, even if available.
187      *             This results then in slower encryption.
188      *
189      * @throws \LengthException if the key length is invalid
190      * @param int $length
191      */
192     public function setKeyLength($length)
193     {
194         switch ($length) {
195             case 128:
196             case 160:
197             case 192:
198             case 224:
199             case 256:
200                 $this->key_length = $length >> 3;
201                 break;
202             default:
203                 throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
204         }
205 
206         parent::setKeyLength($length);
207     }
208 
209     /**
210      * Sets the key.
211      *
212      * Rijndael supports five different key lengths
213      *
214      * @see setKeyLength()
215      * @param string $key
216      * @throws \LengthException if the key length isn't supported
217      */
218     public function setKey($key)
219     {
220         switch (strlen($key)) {
221             case 16:
222             case 20:
223             case 24:
224             case 28:
225             case 32:
226                 break;
227             default:
228                 throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 20, 24, 28 or 32 are supported');
229         }
230 
231         parent::setKey($key);
232     }
233 
234     /**
235      * Sets the block length
236      *
237      * Valid block lengths are 128, 160, 192, 224, and 256.
238      *
239      * @param int $length
240      */
241     public function setBlockLength($length)
242     {
243         switch ($length) {
244             case 128:
245             case 160:
246             case 192:
247             case 224:
248             case 256:
249                 break;
250             default:
251                 throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
252         }
253 
254         $this->Nb = $length >> 5;
255         $this->block_size = $length >> 3;
256         $this->changed = $this->nonIVChanged = true;
257         $this->setEngine();
258     }
259 
260     /**
261      * Test for engine validity
262      *
263      * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
264      *
265      * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
266      * @param int $engine
267      * @return bool
268      */
269     protected function isValidEngineHelper($engine)
270     {
271         switch ($engine) {
272             case self::ENGINE_LIBSODIUM:
273                 return function_exists('sodium_crypto_aead_aes256gcm_is_available') &&
274                        sodium_crypto_aead_aes256gcm_is_available() &&
275                        $this->mode == self::MODE_GCM &&
276                        $this->key_length == 32 &&
277                        $this->nonce && strlen($this->nonce) == 12 &&
278                        $this->block_size == 16;
279             case self::ENGINE_OPENSSL_GCM:
280                 if (!extension_loaded('openssl')) {
281                     return false;
282                 }
283                 $methods = openssl_get_cipher_methods();
284                 return $this->mode == self::MODE_GCM &&
285                        version_compare(PHP_VERSION, '7.1.0', '>=') &&
286                        in_array('aes-' . $this->getKeyLength() . '-gcm', $methods) &&
287                        $this->block_size == 16;
288             case self::ENGINE_OPENSSL:
289                 if ($this->block_size != 16) {
290                     return false;
291                 }
292                 $this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb';
293                 $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->openssl_translate_mode();
294                 break;
295             case self::ENGINE_MCRYPT:
296                 $this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
297                 if ($this->key_length % 8) { // is it a 160/224-bit key?
298                     // mcrypt is not usable for them, only for 128/192/256-bit keys
299                     return false;
300                 }
301         }
302 
303         return parent::isValidEngineHelper($engine);
304     }
305 
306     /**
307      * Encrypts a block
308      *
309      * @param string $in
310      * @return string
311      */
312     protected function encryptBlock($in)
313     {
314         static $tables;
315         if (empty($tables)) {
316             $tables = &$this->getTables();
317         }
318         $t0   = $tables[0];
319         $t1   = $tables[1];
320         $t2   = $tables[2];
321         $t3   = $tables[3];
322         $sbox = $tables[4];
323 
324         $state = [];
325         $words = unpack('N*', $in);
326 
327         $c = $this->c;
328         $w = $this->w;
329         $Nb = $this->Nb;
330         $Nr = $this->Nr;
331 
332         // addRoundKey
333         $wc = $Nb - 1;
334         foreach ($words as $word) {
335             $state[] = $word ^ $w[++$wc];
336         }
337 
338         // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
339         // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
340         // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
341         // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
342         // Unfortunately, the description given there is not quite correct.  Per aes.spec.v316.pdf#page=19 [1],
343         // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
344 
345         // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
346         $temp = [];
347         for ($round = 1; $round < $Nr; ++$round) {
348             $i = 0; // $c[0] == 0
349             $j = $c[1];
350             $k = $c[2];
351             $l = $c[3];
352 
353             while ($i < $Nb) {
354                 $temp[$i] = $t0[$state[$i] >> 24 & 0x000000FF] ^
355                             $t1[$state[$j] >> 16 & 0x000000FF] ^
356                             $t2[$state[$k] >>  8 & 0x000000FF] ^
357                             $t3[$state[$l]       & 0x000000FF] ^
358                             $w[++$wc];
359                 ++$i;
360                 $j = ($j + 1) % $Nb;
361                 $k = ($k + 1) % $Nb;
362                 $l = ($l + 1) % $Nb;
363             }
364             $state = $temp;
365         }
366 
367         // subWord
368         for ($i = 0; $i < $Nb; ++$i) {
369             $state[$i] =   $sbox[$state[$i]       & 0x000000FF]        |
370                           ($sbox[$state[$i] >>  8 & 0x000000FF] <<  8) |
371                           ($sbox[$state[$i] >> 16 & 0x000000FF] << 16) |
372                           ($sbox[$state[$i] >> 24 & 0x000000FF] << 24);
373         }
374 
375         // shiftRows + addRoundKey
376         $i = 0; // $c[0] == 0
377         $j = $c[1];
378         $k = $c[2];
379         $l = $c[3];
380         while ($i < $Nb) {
381             $temp[$i] = ($state[$i] & intval(0xFF000000)) ^
382                         ($state[$j] & 0x00FF0000) ^
383                         ($state[$k] & 0x0000FF00) ^
384                         ($state[$l] & 0x000000FF) ^
385                          $w[$i];
386             ++$i;
387             $j = ($j + 1) % $Nb;
388             $k = ($k + 1) % $Nb;
389             $l = ($l + 1) % $Nb;
390         }
391 
392         return pack('N*', ...$temp);
393     }
394 
395     /**
396      * Decrypts a block
397      *
398      * @param string $in
399      * @return string
400      */
401     protected function decryptBlock($in)
402     {
403         static $invtables;
404         if (empty($invtables)) {
405             $invtables = &$this->getInvTables();
406         }
407         $dt0   = $invtables[0];
408         $dt1   = $invtables[1];
409         $dt2   = $invtables[2];
410         $dt3   = $invtables[3];
411         $isbox = $invtables[4];
412 
413         $state = [];
414         $words = unpack('N*', $in);
415 
416         $c  = $this->c;
417         $dw = $this->dw;
418         $Nb = $this->Nb;
419         $Nr = $this->Nr;
420 
421         // addRoundKey
422         $wc = $Nb - 1;
423         foreach ($words as $word) {
424             $state[] = $word ^ $dw[++$wc];
425         }
426 
427         $temp = [];
428         for ($round = $Nr - 1; $round > 0; --$round) {
429             $i = 0; // $c[0] == 0
430             $j = $Nb - $c[1];
431             $k = $Nb - $c[2];
432             $l = $Nb - $c[3];
433 
434             while ($i < $Nb) {
435                 $temp[$i] = $dt0[$state[$i] >> 24 & 0x000000FF] ^
436                             $dt1[$state[$j] >> 16 & 0x000000FF] ^
437                             $dt2[$state[$k] >>  8 & 0x000000FF] ^
438                             $dt3[$state[$l]       & 0x000000FF] ^
439                             $dw[++$wc];
440                 ++$i;
441                 $j = ($j + 1) % $Nb;
442                 $k = ($k + 1) % $Nb;
443                 $l = ($l + 1) % $Nb;
444             }
445             $state = $temp;
446         }
447 
448         // invShiftRows + invSubWord + addRoundKey
449         $i = 0; // $c[0] == 0
450         $j = $Nb - $c[1];
451         $k = $Nb - $c[2];
452         $l = $Nb - $c[3];
453 
454         while ($i < $Nb) {
455             $word = ($state[$i] & intval(0xFF000000)) |
456                     ($state[$j] & 0x00FF0000) |
457                     ($state[$k] & 0x0000FF00) |
458                     ($state[$l] & 0x000000FF);
459 
460             $temp[$i] = $dw[$i] ^ ($isbox[$word       & 0x000000FF]        |
461                                   ($isbox[$word >>  8 & 0x000000FF] <<  8) |
462                                   ($isbox[$word >> 16 & 0x000000FF] << 16) |
463                                   ($isbox[$word >> 24 & 0x000000FF] << 24));
464             ++$i;
465             $j = ($j + 1) % $Nb;
466             $k = ($k + 1) % $Nb;
467             $l = ($l + 1) % $Nb;
468         }
469 
470         return pack('N*', ...$temp);
471     }
472 
473     /**
474      * Setup the self::ENGINE_INTERNAL $engine
475      *
476      * (re)init, if necessary, the internal cipher $engine and flush all $buffers
477      * Used (only) if $engine == self::ENGINE_INTERNAL
478      *
479      * _setup() will be called each time if $changed === true
480      * typically this happens when using one or more of following public methods:
481      *
482      * - setKey()
483      *
484      * - setIV()
485      *
486      * - disableContinuousBuffer()
487      *
488      * - First run of encrypt() / decrypt() with no init-settings
489      *
490      * {@internal setup() is always called before en/decryption.}
491      *
492      * {@internal Could, but not must, extend by the child Crypt_* class}
493      *
494      * @see self::setKey()
495      * @see self::setIV()
496      * @see self::disableContinuousBuffer()
497      */
498     protected function setup()
499     {
500         if (!$this->changed) {
501             return;
502         }
503 
504         parent::setup();
505 
506         if (is_string($this->iv) && strlen($this->iv) != $this->block_size) {
507             throw new InconsistentSetupException('The IV length (' . strlen($this->iv) . ') does not match the block size (' . $this->block_size . ')');
508         }
509     }
510 
511     /**
512      * Setup the key (expansion)
513      *
514      * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey()
515      */
516     protected function setupKey()
517     {
518         // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
519         // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
520         static $rcon;
521 
522         if (!isset($rcon)) {
523             $rcon = [0,
524                 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
525                 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
526                 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
527                 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
528                 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
529                 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
530             ];
531             $rcon = array_map('intval', $rcon);
532         }
533 
534         if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_length === $this->kl['key_length'] && $this->block_size === $this->kl['block_size']) {
535             // already expanded
536             return;
537         }
538         $this->kl = ['key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size];
539 
540         $this->Nk = $this->key_length >> 2;
541         // see Rijndael-ammended.pdf#page=44
542         $this->Nr = max($this->Nk, $this->Nb) + 6;
543 
544         // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
545         //     "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
546         // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
547         //     "Table 2: Shift offsets for different block lengths"
548         switch ($this->Nb) {
549             case 4:
550             case 5:
551             case 6:
552                 $this->c = [0, 1, 2, 3];
553                 break;
554             case 7:
555                 $this->c = [0, 1, 2, 4];
556                 break;
557             case 8:
558                 $this->c = [0, 1, 3, 4];
559         }
560 
561         $w = array_values(unpack('N*words', $this->key));
562 
563         $length = $this->Nb * ($this->Nr + 1);
564         for ($i = $this->Nk; $i < $length; $i++) {
565             $temp = $w[$i - 1];
566             if ($i % $this->Nk == 0) {
567                 // according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent".
568                 // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
569                 // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
570                 // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
571                 $temp = (($temp << 8) & intval(0xFFFFFF00)) | (($temp >> 24) & 0x000000FF); // rotWord
572                 $temp = $this->subWord($temp) ^ $rcon[$i / $this->Nk];
573             } elseif ($this->Nk > 6 && $i % $this->Nk == 4) {
574                 $temp = $this->subWord($temp);
575             }
576             $w[$i] = $w[$i - $this->Nk] ^ $temp;
577         }
578 
579         // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
580         // and generate the inverse key schedule.  more specifically,
581         // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3),
582         // "The key expansion for the Inverse Cipher is defined as follows:
583         //        1. Apply the Key Expansion.
584         //        2. Apply InvMixColumn to all Round Keys except the first and the last one."
585         // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
586         list($dt0, $dt1, $dt2, $dt3) = $this->getInvTables();
587         $temp = $this->w = $this->dw = [];
588         for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
589             if ($col == $this->Nb) {
590                 if ($row == 0) {
591                     $this->dw[0] = $this->w[0];
592                 } else {
593                     // subWord + invMixColumn + invSubWord = invMixColumn
594                     $j = 0;
595                     while ($j < $this->Nb) {
596                         $dw = $this->subWord($this->w[$row][$j]);
597                         $temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^
598                                     $dt1[$dw >> 16 & 0x000000FF] ^
599                                     $dt2[$dw >>  8 & 0x000000FF] ^
600                                     $dt3[$dw       & 0x000000FF];
601                         $j++;
602                     }
603                     $this->dw[$row] = $temp;
604                 }
605 
606                 $col = 0;
607                 $row++;
608             }
609             $this->w[$row][$col] = $w[$i];
610         }
611 
612         $this->dw[$row] = $this->w[$row];
613 
614         // Converting to 1-dim key arrays (both ascending)
615         $this->dw = array_reverse($this->dw);
616         $w  = array_pop($this->w);
617         $dw = array_pop($this->dw);
618         foreach ($this->w as $r => $wr) {
619             foreach ($wr as $c => $wc) {
620                 $w[]  = $wc;
621                 $dw[] = $this->dw[$r][$c];
622             }
623         }
624         $this->w  = $w;
625         $this->dw = $dw;
626     }
627 
628     /**
629      * Performs S-Box substitutions
630      *
631      * @return array
632      * @param int $word
633      */
634     private function subWord($word)
635     {
636         static $sbox;
637         if (empty($sbox)) {
638             list(, , , , $sbox) = self::getTables();
639         }
640 
641         return  $sbox[$word       & 0x000000FF]        |
642                ($sbox[$word >>  8 & 0x000000FF] <<  8) |
643                ($sbox[$word >> 16 & 0x000000FF] << 16) |
644                ($sbox[$word >> 24 & 0x000000FF] << 24);
645     }
646 
647     /**
648      * Provides the mixColumns and sboxes tables
649      *
650      * @see self::encryptBlock()
651      * @see self::setupInlineCrypt()
652      * @see self::subWord()
653      * @return array &$tables
654      */
655     protected function &getTables()
656     {
657         static $tables;
658         if (empty($tables)) {
659             // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
660             // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
661             // those are the names we'll use.
662             $t3 = array_map('intval', [
663                 // with array_map('intval', ...) we ensure we have only int's and not
664                 // some slower floats converted by php automatically on high values
665                 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
666                 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
667                 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
668                 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
669                 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
670                 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
671                 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
672                 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
673                 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
674                 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
675                 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
676                 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
677                 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
678                 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
679                 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
680                 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
681                 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
682                 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
683                 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
684                 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
685                 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
686                 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
687                 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
688                 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
689                 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
690                 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
691                 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
692                 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
693                 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
694                 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
695                 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
696                 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
697             ]);
698 
699             foreach ($t3 as $t3i) {
700                 $t0[] = (($t3i << 24) & intval(0xFF000000)) | (($t3i >>  8) & 0x00FFFFFF);
701                 $t1[] = (($t3i << 16) & intval(0xFFFF0000)) | (($t3i >> 16) & 0x0000FFFF);
702                 $t2[] = (($t3i <<  8) & intval(0xFFFFFF00)) | (($t3i >> 24) & 0x000000FF);
703             }
704 
705             $tables = [
706                 // The Precomputed mixColumns tables t0 - t3
707                 $t0,
708                 $t1,
709                 $t2,
710                 $t3,
711                 // The SubByte S-Box
712                 [
713                     0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
714                     0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
715                     0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
716                     0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
717                     0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
718                     0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
719                     0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
720                     0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
721                     0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
722                     0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
723                     0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
724                     0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
725                     0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
726                     0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
727                     0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
728                     0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
729                 ]
730             ];
731         }
732         return $tables;
733     }
734 
735     /**
736      * Provides the inverse mixColumns and inverse sboxes tables
737      *
738      * @see self::decryptBlock()
739      * @see self::setupInlineCrypt()
740      * @see self::setupKey()
741      * @return array &$tables
742      */
743     protected function &getInvTables()
744     {
745         static $tables;
746         if (empty($tables)) {
747             $dt3 = array_map('intval', [
748                 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
749                 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
750                 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
751                 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
752                 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
753                 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
754                 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
755                 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
756                 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
757                 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
758                 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
759                 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
760                 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
761                 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
762                 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
763                 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
764                 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
765                 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
766                 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
767                 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
768                 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
769                 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
770                 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
771                 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
772                 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
773                 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
774                 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
775                 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
776                 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
777                 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
778                 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
779                 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
780             ]);
781 
782             foreach ($dt3 as $dt3i) {
783                 $dt0[] = (($dt3i << 24) & intval(0xFF000000)) | (($dt3i >>  8) & 0x00FFFFFF);
784                 $dt1[] = (($dt3i << 16) & intval(0xFFFF0000)) | (($dt3i >> 16) & 0x0000FFFF);
785                 $dt2[] = (($dt3i <<  8) & intval(0xFFFFFF00)) | (($dt3i >> 24) & 0x000000FF);
786             };
787 
788             $tables = [
789                 // The Precomputed inverse mixColumns tables dt0 - dt3
790                 $dt0,
791                 $dt1,
792                 $dt2,
793                 $dt3,
794                 // The inverse SubByte S-Box
795                 [
796                     0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
797                     0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
798                     0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
799                     0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
800                     0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
801                     0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
802                     0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
803                     0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
804                     0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
805                     0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
806                     0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
807                     0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
808                     0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
809                     0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
810                     0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
811                     0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
812                 ]
813             ];
814         }
815         return $tables;
816     }
817 
818     /**
819      * Setup the performance-optimized function for de/encrypt()
820      *
821      * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt()
822      */
823     protected function setupInlineCrypt()
824     {
825         $w  = $this->w;
826         $dw = $this->dw;
827         $init_encrypt = '';
828         $init_decrypt = '';
829 
830         $Nr = $this->Nr;
831         $Nb = $this->Nb;
832         $c  = $this->c;
833 
834         // Generating encrypt code:
835         $init_encrypt .= '
836             if (empty($tables)) {
837                 $tables = &$this->getTables();
838             }
839             $t0   = $tables[0];
840             $t1   = $tables[1];
841             $t2   = $tables[2];
842             $t3   = $tables[3];
843             $sbox = $tables[4];
844         ';
845 
846         $s  = 'e';
847         $e  = 's';
848         $wc = $Nb - 1;
849 
850         // Preround: addRoundKey
851         $encrypt_block = '$in = unpack("N*", $in);' . "\n";
852         for ($i = 0; $i < $Nb; ++$i) {
853             $encrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $w[++$wc] . ";\n";
854         }
855 
856         // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
857         for ($round = 1; $round < $Nr; ++$round) {
858             list($s, $e) = [$e, $s];
859             for ($i = 0; $i < $Nb; ++$i) {
860                 $encrypt_block .=
861                     '$' . $e . $i . ' =
862                     $t0[($' . $s . $i                  . ' >> 24) & 0xff] ^
863                     $t1[($' . $s . (($i + $c[1]) % $Nb) . ' >> 16) & 0xff] ^
864                     $t2[($' . $s . (($i + $c[2]) % $Nb) . ' >>  8) & 0xff] ^
865                     $t3[ $' . $s . (($i + $c[3]) % $Nb) . '        & 0xff] ^
866                     ' . $w[++$wc] . ";\n";
867             }
868         }
869 
870         // Finalround: subWord + shiftRows + addRoundKey
871         for ($i = 0; $i < $Nb; ++$i) {
872             $encrypt_block .=
873                 '$' . $e . $i . ' =
874                  $sbox[ $' . $e . $i . '        & 0xff]        |
875                 ($sbox[($' . $e . $i . ' >>  8) & 0xff] <<  8) |
876                 ($sbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
877                 ($sbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
878         }
879         $encrypt_block .= '$in = pack("N*"' . "\n";
880         for ($i = 0; $i < $Nb; ++$i) {
881             $encrypt_block .= ',
882                 ($' . $e . $i                  . ' & ' . ((int)0xFF000000) . ') ^
883                 ($' . $e . (($i + $c[1]) % $Nb) . ' &         0x00FF0000   ) ^
884                 ($' . $e . (($i + $c[2]) % $Nb) . ' &         0x0000FF00   ) ^
885                 ($' . $e . (($i + $c[3]) % $Nb) . ' &         0x000000FF   ) ^
886                 ' . $w[$i] . "\n";
887         }
888         $encrypt_block .= ');';
889 
890         // Generating decrypt code:
891         $init_decrypt .= '
892             if (empty($invtables)) {
893                 $invtables = &$this->getInvTables();
894             }
895             $dt0   = $invtables[0];
896             $dt1   = $invtables[1];
897             $dt2   = $invtables[2];
898             $dt3   = $invtables[3];
899             $isbox = $invtables[4];
900         ';
901 
902         $s  = 'e';
903         $e  = 's';
904         $wc = $Nb - 1;
905 
906         // Preround: addRoundKey
907         $decrypt_block = '$in = unpack("N*", $in);' . "\n";
908         for ($i = 0; $i < $Nb; ++$i) {
909             $decrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $dw[++$wc] . ';' . "\n";
910         }
911 
912         // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
913         for ($round = 1; $round < $Nr; ++$round) {
914             list($s, $e) = [$e, $s];
915             for ($i = 0; $i < $Nb; ++$i) {
916                 $decrypt_block .=
917                     '$' . $e . $i . ' =
918                     $dt0[($' . $s . $i                        . ' >> 24) & 0xff] ^
919                     $dt1[($' . $s . (($Nb + $i - $c[1]) % $Nb) . ' >> 16) & 0xff] ^
920                     $dt2[($' . $s . (($Nb + $i - $c[2]) % $Nb) . ' >>  8) & 0xff] ^
921                     $dt3[ $' . $s . (($Nb + $i - $c[3]) % $Nb) . '        & 0xff] ^
922                     ' . $dw[++$wc] . ";\n";
923             }
924         }
925 
926         // Finalround: subWord + shiftRows + addRoundKey
927         for ($i = 0; $i < $Nb; ++$i) {
928             $decrypt_block .=
929                 '$' . $e . $i . ' =
930                  $isbox[ $' . $e . $i . '        & 0xff]        |
931                 ($isbox[($' . $e . $i . ' >>  8) & 0xff] <<  8) |
932                 ($isbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
933                 ($isbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
934         }
935         $decrypt_block .= '$in = pack("N*"' . "\n";
936         for ($i = 0; $i < $Nb; ++$i) {
937             $decrypt_block .= ',
938                 ($' . $e . $i .                        ' & ' . ((int)0xFF000000) . ') ^
939                 ($' . $e . (($Nb + $i - $c[1]) % $Nb) . ' &         0x00FF0000   ) ^
940                 ($' . $e . (($Nb + $i - $c[2]) % $Nb) . ' &         0x0000FF00   ) ^
941                 ($' . $e . (($Nb + $i - $c[3]) % $Nb) . ' &         0x000000FF   ) ^
942                 ' . $dw[$i] . "\n";
943         }
944         $decrypt_block .= ');';
945 
946         $this->inline_crypt = $this->createInlineCryptFunction(
947             [
948                'init_crypt'    => 'static $tables; static $invtables;',
949                'init_encrypt'  => $init_encrypt,
950                'init_decrypt'  => $init_decrypt,
951                'encrypt_block' => $encrypt_block,
952                'decrypt_block' => $decrypt_block
953             ]
954         );
955     }
956 
957     /**
958      * Encrypts a message.
959      *
960      * @see self::decrypt()
961      * @see parent::encrypt()
962      * @param string $plaintext
963      * @return string
964      */
965     public function encrypt($plaintext)
966     {
967         $this->setup();
968 
969         switch ($this->engine) {
970             case self::ENGINE_LIBSODIUM:
971                 $this->newtag = sodium_crypto_aead_aes256gcm_encrypt($plaintext, $this->aad, $this->nonce, $this->key);
972                 return Strings::shift($this->newtag, strlen($plaintext));
973             case self::ENGINE_OPENSSL_GCM:
974                 return openssl_encrypt(
975                     $plaintext,
976                     'aes-' . $this->getKeyLength() . '-gcm',
977                     $this->key,
978                     OPENSSL_RAW_DATA,
979                     $this->nonce,
980                     $this->newtag,
981                     $this->aad
982                 );
983         }
984 
985         return parent::encrypt($plaintext);
986     }
987 
988     /**
989      * Decrypts a message.
990      *
991      * @see self::encrypt()
992      * @see parent::decrypt()
993      * @param string $ciphertext
994      * @return string
995      */
996     public function decrypt($ciphertext)
997     {
998         $this->setup();
999 
1000         switch ($this->engine) {
1001             case self::ENGINE_LIBSODIUM:
1002                 if ($this->oldtag === false) {
1003                     throw new InsufficientSetupException('Authentication Tag has not been set');
1004                 }
1005                 if (strlen($this->oldtag) != 16) {
1006                     break;
1007                 }
1008                 $plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext . $this->oldtag, $this->aad, $this->nonce, $this->key);
1009                 if ($plaintext === false) {
1010                     $this->oldtag = false;
1011                     throw new BadDecryptionException('Error decrypting ciphertext with libsodium');
1012                 }
1013                 return $plaintext;
1014             case self::ENGINE_OPENSSL_GCM:
1015                 if ($this->oldtag === false) {
1016                     throw new InsufficientSetupException('Authentication Tag has not been set');
1017                 }
1018                 $plaintext = openssl_decrypt(
1019                     $ciphertext,
1020                     'aes-' . $this->getKeyLength() . '-gcm',
1021                     $this->key,
1022                     OPENSSL_RAW_DATA,
1023                     $this->nonce,
1024                     $this->oldtag,
1025                     $this->aad
1026                 );
1027                 if ($plaintext === false) {
1028                     $this->oldtag = false;
1029                     throw new BadDecryptionException('Error decrypting ciphertext with OpenSSL');
1030                 }
1031                 return $plaintext;
1032         }
1033 
1034         return parent::decrypt($ciphertext);
1035     }
1036 }
1037