if (class_exists('ParagonIE_Sodium_Core_AES', false)) {
* Bitsliced implementation of the AES block cipher.
* Based on the implementation provided by BearSSL.
* @internal This should only be used by sodium_compat
class ParagonIE_Sodium_Core_AES extends ParagonIE_Sodium_Core_Util
* @var int[] AES round constants
private static $Rcon = array(
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36
* Mutates the values of $q!
* @param ParagonIE_Sodium_Core_AES_Block $q
public static function sbox(ParagonIE_Sodium_Core_AES_Block $q)
$x0 = $q[7] & self::U32_MAX;
$x1 = $q[6] & self::U32_MAX;
$x2 = $q[5] & self::U32_MAX;
$x3 = $q[4] & self::U32_MAX;
$x4 = $q[3] & self::U32_MAX;
$x5 = $q[2] & self::U32_MAX;
$x6 = $q[1] & self::U32_MAX;
$x7 = $q[0] & self::U32_MAX;
* Bottom linear transformation.
$q[7] = $s0 & self::U32_MAX;
$q[6] = $s1 & self::U32_MAX;
$q[5] = $s2 & self::U32_MAX;
$q[4] = $s3 & self::U32_MAX;
$q[3] = $s4 & self::U32_MAX;
$q[2] = $s5 & self::U32_MAX;
$q[1] = $s6 & self::U32_MAX;
$q[0] = $s7 & self::U32_MAX;
* Mutates the values of $q!
* @param ParagonIE_Sodium_Core_AES_Block $q
public static function invSbox(ParagonIE_Sodium_Core_AES_Block $q)
self::processInversion($q);
self::processInversion($q);
* This is some boilerplate code needed to invert an S-box. Rather than repeat the code
* twice, I moved it to a protected method.
* @param ParagonIE_Sodium_Core_AES_Block $q
protected static function processInversion(ParagonIE_Sodium_Core_AES_Block $q)
$q0 = (~$q[0]) & self::U32_MAX;
$q1 = (~$q[1]) & self::U32_MAX;
$q2 = $q[2] & self::U32_MAX;
$q3 = $q[3] & self::U32_MAX;
$q4 = $q[4] & self::U32_MAX;
$q5 = (~$q[5]) & self::U32_MAX;
$q6 = (~$q[6]) & self::U32_MAX;
$q7 = $q[7] & self::U32_MAX;
$q[7] = ($q1 ^ $q4 ^ $q6) & self::U32_MAX;
$q[6] = ($q0 ^ $q3 ^ $q5) & self::U32_MAX;
$q[5] = ($q7 ^ $q2 ^ $q4) & self::U32_MAX;
$q[4] = ($q6 ^ $q1 ^ $q3) & self::U32_MAX;
$q[3] = ($q5 ^ $q0 ^ $q2) & self::U32_MAX;
$q[2] = ($q4 ^ $q7 ^ $q1) & self::U32_MAX;
$q[1] = ($q3 ^ $q6 ^ $q0) & self::U32_MAX;
$q[0] = ($q2 ^ $q5 ^ $q7) & self::U32_MAX;
public static function subWord($x)
$q = ParagonIE_Sodium_Core_AES_Block::fromArray(
array($x, $x, $x, $x, $x, $x, $x, $x)
return $q[0] & self::U32_MAX;
* Calculate the key schedule from a given random key
* @return ParagonIE_Sodium_Core_AES_KeySchedule
* @throws SodiumException
public static function keySchedule($key)
$key_len = self::strlen($key);
throw new SodiumException('Invalid key length: ' . $key_len);
$nkf = ($num_rounds + 1) << 2;
for ($i = 0; $i < $nk; ++$i) {
$tmp = self::load_4(self::substr($key, $i << 2, 4));
$skey[($i << 1) + 1] = $tmp;
for ($i = $nk, $j = 0, $k = 0; $i < $nkf; ++$i) {
$tmp = (($tmp & 0xff) << 24) | ($tmp >> 8);
$tmp = (self::subWord($tmp) ^ self::$Rcon[$k]) & self::U32_MAX;
} elseif ($nk > 6 && $j === 4) {
$tmp = self::subWord($tmp);
$tmp ^= $skey[($i - $nk) << 1];
$skey[($i << 1)] = $tmp & self::U32_MAX;
$skey[($i << 1) + 1] = $tmp & self::U32_MAX;
/** @psalm-suppress LoopInvalidation */
for ($i = 0; $i < $nkf; $i += 4) {
$q = ParagonIE_Sodium_Core_AES_Block::fromArray(
array_slice($skey, $i << 1, 8)
// We have to overwrite $skey since we're not using C pointers like BearSSL did
for ($j = 0; $j < 8; ++$j) {
$skey[($i << 1) + $j] = $q[$j];
for ($i = 0, $j = 0; $i < $nkf; ++$i, $j += 2) {
$comp_skey[$i] = ($skey[$j] & 0x55555555)
| ($skey[$j + 1] & 0xAAAAAAAA);
return new ParagonIE_Sodium_Core_AES_KeySchedule($comp_skey, $num_rounds);
* @param ParagonIE_Sodium_Core_AES_KeySchedule $skey
* @param ParagonIE_Sodium_Core_AES_Block $q
public static function addRoundKey(
ParagonIE_Sodium_Core_AES_Block $q,
ParagonIE_Sodium_Core_AES_KeySchedule $skey,
$block = $skey->getRoundKey($offset);
for ($j = 0; $j < 8; ++$j) {
$q[$j] = ($q[$j] ^ $block[$j]) & ParagonIE_Sodium_Core_Util::U32_MAX;
* This mainly exists for testing, as we need the round key features for AEGIS.
* @throws SodiumException
public static function decryptBlockECB($message, $key)
if (self::strlen($message) !== 16) {
throw new SodiumException('decryptBlockECB() expects a 16 byte message');
$skey = self::keySchedule($key)->expand();
$q = ParagonIE_Sodium_Core_AES_Block::init();
$q[0] = self::load_4(self::substr($message, 0, 4));
$q[2] = self::load_4(self::substr($message, 4, 4));
$q[4] = self::load_4(self::substr($message, 8, 4));
$q[6] = self::load_4(self::substr($message, 12, 4));
self::bitsliceDecryptBlock($skey, $q);
return self::store32_le($q[0]) .
self::store32_le($q[2]) .
self::store32_le($q[4]) .
* This mainly exists for testing, as we need the round key features for AEGIS.
* @throws SodiumException
public static function encryptBlockECB($message, $key)
if (self::strlen($message) !== 16) {
throw new SodiumException('encryptBlockECB() expects a 16 byte message');
$comp_skey = self::keySchedule($key);
$skey = $comp_skey->expand();
$q = ParagonIE_Sodium_Core_AES_Block::init();
$q[0] = self::load_4(self::substr($message, 0, 4));
$q[2] = self::load_4(self::substr($message, 4, 4));
$q[4] = self::load_4(self::substr($message, 8, 4));
$q[6] = self::load_4(self::substr($message, 12, 4));
self::bitsliceEncryptBlock($skey, $q);
return self::store32_le($q[0]) .
self::store32_le($q[2]) .
self::store32_le($q[4]) .
* @param ParagonIE_Sodium_Core_AES_Expanded $skey
* @param ParagonIE_Sodium_Core_AES_Block $q
public static function bitsliceEncryptBlock(
ParagonIE_Sodium_Core_AES_Expanded $skey,
ParagonIE_Sodium_Core_AES_Block $q
self::addRoundKey($q, $skey);
for ($u = 1; $u < $skey->getNumRounds(); ++$u) {
self::addRoundKey($q, $skey, ($u << 3));
self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3));
public static function aesRound($x, $y)
$q = ParagonIE_Sodium_Core_AES_Block::init();
$q[0] = self::load_4(self::substr($x, 0, 4));
$q[2] = self::load_4(self::substr($x, 4, 4));
$q[4] = self::load_4(self::substr($x, 8, 4));
$q[6] = self::load_4(self::substr($x, 12, 4));
$rk = ParagonIE_Sodium_Core_AES_Block::init();
$rk[0] = $rk[1] = self::load_4(self::substr($y, 0, 4));
$rk[2] = $rk[3] = self::load_4(self::substr($y, 4, 4));
$rk[4] = $rk[5] = self::load_4(self::substr($y, 8, 4));
$rk[6] = $rk[7] = self::load_4(self::substr($y, 12, 4));
// add round key without key schedule:
for ($i = 0; $i < 8; ++$i) {
return self::store32_le($q[0]) .
self::store32_le($q[2]) .
self::store32_le($q[4]) .
* Process two AES blocks in one shot.
* @param string $b0 First AES block
* @param string $rk0 First round key
* @param string $b1 Second AES block
* @param string $rk1 Second round key
public static function doubleRound($b0, $rk0, $b1, $rk1)
$q = ParagonIE_Sodium_Core_AES_Block::init();
$q[0] = self::load_4(self::substr($b0, 0, 4));
$q[2] = self::load_4(self::substr($b0, 4, 4));
$q[4] = self::load_4(self::substr($b0, 8, 4));
$q[6] = self::load_4(self::substr($b0, 12, 4));
$q[1] = self::load_4(self::substr($b1, 0, 4));
$q[3] = self::load_4(self::substr($b1, 4, 4));
$q[5] = self::load_4(self::substr($b1, 8, 4));
$q[7] = self::load_4(self::substr($b1, 12, 4));;
$rk = ParagonIE_Sodium_Core_AES_Block::init();
$rk[0] = self::load_4(self::substr($rk0, 0, 4));
$rk[2] = self::load_4(self::substr($rk0, 4, 4));
$rk[4] = self::load_4(self::substr($rk0, 8, 4));
$rk[6] = self::load_4(self::substr($rk0, 12, 4));
$rk[1] = self::load_4(self::substr($rk1, 0, 4));
$rk[3] = self::load_4(self::substr($rk1, 4, 4));
$rk[5] = self::load_4(self::substr($rk1, 8, 4));
$rk[7] = self::load_4(self::substr($rk1, 12, 4));
// add round key without key schedule:
for ($i = 0; $i < 8; ++$i) {
self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]),
self::store32_le($q[1]) . self::store32_le($q[3]) . self::store32_le($q[5]) . self::store32_le($q[7]),
* @param ParagonIE_Sodium_Core_AES_Expanded $skey
* @param ParagonIE_Sodium_Core_AES_Block $q