From 67fadea78b6200d863d52e00d1bbe29419e6f642 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Wed, 17 Apr 2024 03:11:33 -0400 Subject: [PATCH] Implement AEGIS-128L and AEGIS-256 --- src/Core/AEGIS/State128L.php | 275 ++++++++++++++++++++++++ src/Core/AEGIS/State256.php | 232 +++++++++++++++++++++ src/Core/AEGIS128L.php | 119 +++++++++++ src/Core/AEGIS256.php | 118 +++++++++++ src/Core/AES.php | 34 +++ src/Core/Util.php | 22 ++ tests/unit/AEGISTest.php | 390 +++++++++++++++++++++++++++++++++++ tests/unit/AESTest.php | 12 ++ tests/unit/UtilTest.php | 17 ++ 9 files changed, 1219 insertions(+) create mode 100644 src/Core/AEGIS/State128L.php create mode 100644 src/Core/AEGIS/State256.php create mode 100644 src/Core/AEGIS128L.php create mode 100644 src/Core/AEGIS256.php create mode 100644 tests/unit/AEGISTest.php diff --git a/src/Core/AEGIS/State128L.php b/src/Core/AEGIS/State128L.php new file mode 100644 index 00000000..0bbf99d2 --- /dev/null +++ b/src/Core/AEGIS/State128L.php @@ -0,0 +1,275 @@ + $state */ + protected $state; + public function __construct() + { + $this->state = array_fill(0, 8, ''); + } + + /** + * @internal Only use this for unit tests! + * @return string[] + */ + public function getState() + { + return array_values($this->state); + } + + /** + * @param array $input + * @return self + * @throws SodiumException + * + * @internal Only for unit tests + */ + public static function initForUnitTests(array $input) + { + if (count($input) < 8) { + throw new SodiumException('invalid input'); + } + $state = new self(); + for ($i = 0; $i < 8; ++$i) { + $state->state[$i] = $input[$i]; + } + return $state; + } + + /** + * @param string $key + * @param string $nonce + * @return self + */ + public static function init($key, $nonce) + { + $state = new self(); + + // S0 = key ^ nonce + $state->state[0] = $key ^ $nonce; + // S1 = C1 + $state->state[1] = SODIUM_COMPAT_AEGIS_C1; + // S2 = C0 + $state->state[2] = SODIUM_COMPAT_AEGIS_C0; + // S3 = C1 + $state->state[3] = SODIUM_COMPAT_AEGIS_C1; + // S4 = key ^ nonce + $state->state[4] = $key ^ $nonce; + // S5 = key ^ C0 + $state->state[5] = $key ^ SODIUM_COMPAT_AEGIS_C0; + // S6 = key ^ C1 + $state->state[6] = $key ^ SODIUM_COMPAT_AEGIS_C1; + // S7 = key ^ C0 + $state->state[7] = $key ^ SODIUM_COMPAT_AEGIS_C0; + + // Repeat(10, Update(nonce, key)) + for ($i = 0; $i < 10; ++$i) { + $state->update($nonce, $key); + } + return $state; + } + + /** + * @param string $ai + * @return self + */ + public function absorb($ai) + { + if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + $t0 = ParagonIE_Sodium_Core_Util::substr($ai, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($ai, 16, 16); + return $this->update($t0, $t1); + } + + + /** + * @param string $ci + * @return string + * @throws SodiumException + */ + public function dec($ci) + { + if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(xi, 128) + $t0 = ParagonIE_Sodium_Core_Util::substr($ci, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($ci, 16, 16); + + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // Update(out0, out1) + // xi = out0 || out1 + $this->update($out0, $out1); + return $out0 . $out1; + } + + /** + * @param string $cn + * @return string + */ + public function decPartial($cn) + { + $len = ParagonIE_Sodium_Core_Util::strlen($cn); + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(ZeroPad(cn, 256), 128) + $cn = str_pad($cn, 32, "\0", STR_PAD_RIGHT); + $t0 = ParagonIE_Sodium_Core_Util::substr($cn, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($cn, 16, 16); + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // xn = Truncate(out0 || out1, |cn|) + $xn = ParagonIE_Sodium_Core_Util::substr($out0 . $out1, 0, $len); + + // v0, v1 = Split(ZeroPad(xn, 256), 128) + $padded = str_pad($xn, 32, "\0", STR_PAD_RIGHT); + $v0 = ParagonIE_Sodium_Core_Util::substr($padded, 0, 16); + $v1 = ParagonIE_Sodium_Core_Util::substr($padded, 16, 16); + // Update(v0, v1) + $this->update($v0, $v1); + + // return xn + return $xn; + } + + /** + * @param string $xi + * @return string + * @throws SodiumException + */ + public function enc($xi) + { + if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(xi, 128) + $t0 = ParagonIE_Sodium_Core_Util::substr($xi, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($xi, 16, 16); + + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // Update(t0, t1) + // ci = out0 || out1 + $this->update($t0, $t1); + + // return ci + return $out0 . $out1; + } + + /** + * @param int $ad_len_bits + * @param int $msg_len_bits + * @return string + */ + public function finalize($ad_len_bits, $msg_len_bits) + { + $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . + ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); + $t = $this->state[2] ^ $encoded; + for ($i = 0; $i < 7; ++$i) { + $this->update($t, $t); + } + return ($this->state[0] ^ $this->state[1] ^ $this->state[2] ^ $this->state[3]) . + ($this->state[4] ^ $this->state[5] ^ $this->state[6] ^ $this->state[7]); + } + + /** + * @param string $m0 + * @param string $m1 + * @return self + */ + public function update($m0, $m1) + { + /* + S'0 = AESRound(S7, S0 ^ M0) + S'1 = AESRound(S0, S1) + S'2 = AESRound(S1, S2) + S'3 = AESRound(S2, S3) + S'4 = AESRound(S3, S4 ^ M1) + S'5 = AESRound(S4, S5) + S'6 = AESRound(S5, S6) + S'7 = AESRound(S6, S7) + */ + $s_0 = ParagonIE_Sodium_Core_AES::aesRound( + $this->state[7], + $this->state[0] ^ $m0 + ); + $s_1 = ParagonIE_Sodium_Core_AES::aesRound($this->state[0], $this->state[1]); + $s_2 = ParagonIE_Sodium_Core_AES::aesRound($this->state[1], $this->state[2]); + $s_3 = ParagonIE_Sodium_Core_AES::aesRound($this->state[2], $this->state[3]); + $s_4 = ParagonIE_Sodium_Core_AES::aesRound( + $this->state[3], + $this->state[4] ^ $m1 + ); + $s_5 = ParagonIE_Sodium_Core_AES::aesRound($this->state[4], $this->state[5]); + $s_6 = ParagonIE_Sodium_Core_AES::aesRound($this->state[5], $this->state[6]); + $s_7 = ParagonIE_Sodium_Core_AES::aesRound($this->state[6], $this->state[7]); + + /* + S0 = S'0 + S1 = S'1 + S2 = S'2 + S3 = S'3 + S4 = S'4 + S5 = S'5 + S6 = S'6 + S7 = S'7 + */ + $this->state[0] = $s_0; + $this->state[1] = $s_1; + $this->state[2] = $s_2; + $this->state[3] = $s_3; + $this->state[4] = $s_4; + $this->state[5] = $s_5; + $this->state[6] = $s_6; + $this->state[7] = $s_7; + return $this; + } +} \ No newline at end of file diff --git a/src/Core/AEGIS/State256.php b/src/Core/AEGIS/State256.php new file mode 100644 index 00000000..7c11cf73 --- /dev/null +++ b/src/Core/AEGIS/State256.php @@ -0,0 +1,232 @@ + $state */ + protected $state; + public function __construct() + { + $this->state = array_fill(0, 6, ''); + } + + /** + * @internal Only use this for unit tests! + * @return string[] + */ + public function getState() + { + return array_values($this->state); + } + + /** + * @param array $input + * @return self + * @throws SodiumException + * + * @internal Only for unit tests + */ + public static function initForUnitTests(array $input) + { + if (count($input) < 6) { + throw new SodiumException('invalid input'); + } + $state = new self(); + for ($i = 0; $i < 6; ++$i) { + $state->state[$i] = $input[$i]; + } + return $state; + } + + /** + * @param string $key + * @param string $nonce + * @return self + */ + public static function init($key, $nonce) + { + $state = new self(); + $k0 = ParagonIE_Sodium_Core_Util::substr($key, 0, 16); + $k1 = ParagonIE_Sodium_Core_Util::substr($key, 16, 16); + $n0 = ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16); + $n1 = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 16); + + // S0 = k0 ^ n0 + // S1 = k1 ^ n1 + // S2 = C1 + // S3 = C0 + // S4 = k0 ^ C0 + // S5 = k1 ^ C1 + $k0_n0 = $k0 ^ $n0; + $k1_n1 = $k1 ^ $n1; + $state->state[0] = $k0_n0; + $state->state[1] = $k1_n1; + $state->state[2] = SODIUM_COMPAT_AEGIS_C1; + $state->state[3] = SODIUM_COMPAT_AEGIS_C0; + $state->state[4] = $k0 ^ SODIUM_COMPAT_AEGIS_C0; + $state->state[5] = $k1 ^ SODIUM_COMPAT_AEGIS_C1; + + // Repeat(4, + // Update(k0) + // Update(k1) + // Update(k0 ^ n0) + // Update(k1 ^ n1) + // ) + for ($i = 0; $i < 4; ++$i) { + $state->update($k0); + $state->update($k1); + $state->update($k0 ^ $n0); + $state->update($k1 ^ $n1); + } + return $state; + } + + /** + * @param string $ai + * @return self + * @throws SodiumException + */ + public function absorb($ai) + { + if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + return $this->update($ai); + } + + /** + * @param string $ci + * @return string + * @throws SodiumException + */ + public function dec($ci) + { + if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + $xi = $ci ^ $z; + $this->update($xi); + return $xi; + } + + /** + * @param string $cn + * @return string + */ + public function decPartial($cn) + { + $len = ParagonIE_Sodium_Core_Util::strlen($cn); + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + + // t = ZeroPad(cn, 128) + $t = str_pad($cn, 16, "\0", STR_PAD_RIGHT); + + // out = t ^ z + $out = $t ^ $z; + + // xn = Truncate(out, |cn|) + $xn = ParagonIE_Sodium_Core_Util::substr($out, 0, $len); + + // v = ZeroPad(xn, 128) + $v = str_pad($xn, 16, "\0", STR_PAD_RIGHT); + // Update(v) + $this->update($v); + + // return xn + return $xn; + } + + /** + * @param string $xi + * @return string + * @throws SodiumException + */ + public function enc($xi) + { + if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + $this->update($xi); + return $xi ^ $z; + } + + /** + * @param int $ad_len_bits + * @param int $msg_len_bits + * @return string + */ + public function finalize($ad_len_bits, $msg_len_bits) + { + $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . + ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); + $t = $this->state[3] ^ $encoded; + + for ($i = 0; $i < 7; ++$i) { + $this->update($t); + } + + return ($this->state[0] ^ $this->state[1] ^ $this->state[2]) . + ($this->state[3] ^ $this->state[4] ^ $this->state[5]); + } + + /** + * @param string $m + * @return self + */ + public function update($m) + { + /* + S'0 = AESRound(S5, S0 ^ M) + S'1 = AESRound(S0, S1) + S'2 = AESRound(S1, S2) + S'3 = AESRound(S2, S3) + S'4 = AESRound(S3, S4) + S'5 = AESRound(S4, S5) + */ + $s_0 = ParagonIE_Sodium_Core_AES::aesRound( + $this->state[5], + $this->state[0] ^ $m + ); + $s_1 = ParagonIE_Sodium_Core_AES::aesRound($this->state[0], $this->state[1]); + $s_2 = ParagonIE_Sodium_Core_AES::aesRound($this->state[1], $this->state[2]); + $s_3 = ParagonIE_Sodium_Core_AES::aesRound($this->state[2], $this->state[3]); + $s_4 = ParagonIE_Sodium_Core_AES::aesRound($this->state[3], $this->state[4]); + $s_5 = ParagonIE_Sodium_Core_AES::aesRound($this->state[4], $this->state[5]); + + /* + S0 = S'0 + S1 = S'1 + S2 = S'2 + S3 = S'3 + S4 = S'4 + S5 = S'5 + */ + $this->state[0] = $s_0; + $this->state[1] = $s_1; + $this->state[2] = $s_2; + $this->state[3] = $s_3; + $this->state[4] = $s_4; + $this->state[5] = $s_5; + return $this; + } +} diff --git a/src/Core/AEGIS128L.php b/src/Core/AEGIS128L.php new file mode 100644 index 00000000..ad1e85d3 --- /dev/null +++ b/src/Core/AEGIS128L.php @@ -0,0 +1,119 @@ +> 5; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 5, 32); + if (self::strlen($ai) < 32) { + $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $msg = ''; + $cn = self::strlen($ct) & 31; + $ct_blocks = self::strlen($ct) >> 5; + for ($i = 0; $i < $ct_blocks; ++$i) { + $msg .= $state->dec(self::substr($ct, $i << 5, 32)); + } + if ($cn) { + $start = $ct_blocks << 5; + $msg .= $state->decPartial(self::substr($ct, $start, $cn)); + } + $expected_tag = $state->finalize( + self::strlen($ad) << 3, + self::strlen($msg) << 3 + ); + if (!self::hashEquals($expected_tag, $tag)) { + try { + // The RFC says to erase msg, so we shall try: + ParagonIE_Sodium_Compat::memzero($msg); + } catch (SodiumException $ex) { + // Do nothing if we cannot memzero + } + throw new SodiumException('verification failed'); + } + return $msg; + } + + /** + * @param string $msg + * @param string $ad + * @param string $key + * @param string $nonce + * @return array + * + * @throws SodiumException + */ + public static function encrypt($msg, $ad, $key, $nonce) + { + $state = self::init($key, $nonce); + // ad_blocks = Split(ZeroPad(ad, 256), 256) + // for ai in ad_blocks: + // Absorb(ai) + $ad_len = self::strlen($ad); + $msg_len = self::strlen($msg); + $ad_blocks = ($ad_len + 31) >> 5; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 5, 32); + if (self::strlen($ai) < 32) { + $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + // msg_blocks = Split(ZeroPad(msg, 256), 256) + // for xi in msg_blocks: + // ct = ct || Enc(xi) + $ct = ''; + $msg_blocks = ($msg_len + 31) >> 5; + for ($i = 0; $i < $msg_blocks; ++$i) { + $xi = self::substr($msg, $i << 5, 32); + if (self::strlen($xi) < 32) { + $xi = str_pad($xi, 32, "\0", STR_PAD_RIGHT); + } + $ct .= $state->enc($xi); + } + // tag = Finalize(|ad|, |msg|) + // ct = Truncate(ct, |msg|) + $tag = $state->finalize( + $ad_len << 3, + $msg_len << 3 + ); + // return ct and tag + return array( + self::substr($ct, 0, $msg_len), + $tag + ); + } + + /** + * @param string $key + * @param string $nonce + * @return ParagonIE_Sodium_Core_AEGIS_State128L + */ + public static function init($key, $nonce) + { + return ParagonIE_Sodium_Core_AEGIS_State128L::init($key, $nonce); + } +} diff --git a/src/Core/AEGIS256.php b/src/Core/AEGIS256.php new file mode 100644 index 00000000..605bbcaf --- /dev/null +++ b/src/Core/AEGIS256.php @@ -0,0 +1,118 @@ +> 4; + // for ai in ad_blocks: + // Absorb(ai) + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 4, 16); + if (self::strlen($ai) < 16) { + $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $msg = ''; + $cn = self::strlen($ct) & 15; + $ct_blocks = self::strlen($ct) >> 4; + // ct_blocks = Split(ZeroPad(ct, 128), 128) + // cn = Tail(ct, |ct| mod 128) + for ($i = 0; $i < $ct_blocks; ++$i) { + $msg .= $state->dec(self::substr($ct, $i << 4, 16)); + } + // if cn is not empty: + // msg = msg || DecPartial(cn) + if ($cn) { + $start = $ct_blocks << 4; + $msg .= $state->decPartial(self::substr($ct, $start, $cn)); + } + $expected_tag = $state->finalize( + self::strlen($ad) << 3, + self::strlen($msg) << 3 + ); + if (!self::hashEquals($expected_tag, $tag)) { + try { + // The RFC says to erase msg, so we shall try: + ParagonIE_Sodium_Compat::memzero($msg); + } catch (SodiumException $ex) { + // Do nothing if we cannot memzero + } + throw new SodiumException('verification failed'); + } + return $msg; + } + + /** + * @param string $msg + * @param string $ad + * @param string $key + * @param string $nonce + * @return array + * @throws SodiumException + */ + public static function encrypt($msg, $ad, $key, $nonce) + { + $state = self::init($key, $nonce); + $ad_len = self::strlen($ad); + $msg_len = self::strlen($msg); + $ad_blocks = ($ad_len + 15) >> 4; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 4, 16); + if (self::strlen($ai) < 16) { + $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $ct = ''; + $msg_blocks = ($msg_len + 15) >> 4; + for ($i = 0; $i < $msg_blocks; ++$i) { + $xi = self::substr($msg, $i << 4, 16); + if (self::strlen($xi) < 16) { + $xi = str_pad($xi, 16, "\0", STR_PAD_RIGHT); + } + $ct .= $state->enc($xi); + } + $tag = $state->finalize( + $ad_len << 3, + $msg_len << 3 + ); + return array( + self::substr($ct, 0, $msg_len), + $tag + ); + + } + + /** + * @param string $key + * @param string $nonce + * @return ParagonIE_Sodium_Core_AEGIS_State256 + */ + public static function init($key, $nonce) + { + return ParagonIE_Sodium_Core_AEGIS_State256::init($key, $nonce); + } +} diff --git a/src/Core/AES.php b/src/Core/AES.php index 13c01d81..a7b75499 100644 --- a/src/Core/AES.php +++ b/src/Core/AES.php @@ -407,6 +407,40 @@ public static function bitsliceEncryptBlock( self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3)); } + /** + * @param string $x + * @param string $y + * @return string + */ + 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)); + + $q->orthogonalize(); + self::sbox($q); + $q->shiftRows(); + $q->mixColumns(); + $q->orthogonalize(); + // add round key without key schedule: + for ($i = 0; $i < 8; ++$i) { + $q[$i] ^= $rk[$i]; + } + return self::store32_le($q[0]) . + self::store32_le($q[2]) . + self::store32_le($q[4]) . + self::store32_le($q[6]); + } + /** * @param ParagonIE_Sodium_Core_AES_Expanded $skey * @param ParagonIE_Sodium_Core_AES_Block $q diff --git a/src/Core/Util.php b/src/Core/Util.php index 2903beff..e5d96dcd 100644 --- a/src/Core/Util.php +++ b/src/Core/Util.php @@ -35,6 +35,28 @@ public static function abs($integer, $size = 0) ); } + /** + * @param string $a + * @param string $b + * @return string + * @throws SodiumException + */ + public static function andStrings($a, $b) + { + /* Type checks: */ + if (!is_string($a)) { + throw new TypeError('Argument 1 must be a string'); + } + if (!is_string($b)) { + throw new TypeError('Argument 2 must be a string'); + } + $len = self::strlen($a); + if (self::strlen($b) !== $len) { + throw new SodiumException('Both strings must be of equal length to combine with bitwise AND'); + } + return $a & $b; + } + /** * Convert a binary string into a hexadecimal string without cache-timing * leaks diff --git a/tests/unit/AEGISTest.php b/tests/unit/AEGISTest.php new file mode 100644 index 00000000..2af92d03 --- /dev/null +++ b/tests/unit/AEGISTest.php @@ -0,0 +1,390 @@ +update($m0, $m1); + $s = $state->getState(); + $expected = array( + ParagonIE_Sodium_Core_Util::hex2bin('596ab773e4433ca0127c73f60536769d'), + ParagonIE_Sodium_Core_Util::hex2bin('790394041a3d26ab697bde865014652d'), + ParagonIE_Sodium_Core_Util::hex2bin('38cf49e4b65248acd533041b64dd0611'), + ParagonIE_Sodium_Core_Util::hex2bin('16d8e58748f437bfff1797f780337cee'), + ParagonIE_Sodium_Core_Util::hex2bin('69761320f7dd738b281cc9f335ac2f5a'), + ParagonIE_Sodium_Core_Util::hex2bin('a21746bb193a569e331e1aa985d0d729'), + ParagonIE_Sodium_Core_Util::hex2bin('09d714e6fcf9177a8ed1cde7e3d259a6'), + ParagonIE_Sodium_Core_Util::hex2bin('61279ba73167f0ab76f0a11bf203bdff') + ); + $this->assertSame($s, $expected); + } + + public function testAegis256lUpdate() + { + $state = ParagonIE_Sodium_Core_AEGIS_State256::initForUnitTests(array( + ParagonIE_Sodium_Core_Util::hex2bin('1fa1207ed76c86f2c4bb40e8b395b43e'), + ParagonIE_Sodium_Core_Util::hex2bin('b44c375e6c1e1978db64bcd12e9e332f'), + ParagonIE_Sodium_Core_Util::hex2bin('0dab84bfa9f0226432ff630f233d4e5b'), + ParagonIE_Sodium_Core_Util::hex2bin('d7ef65c9b93e8ee60c75161407b066e7'), + ParagonIE_Sodium_Core_Util::hex2bin('a760bb3da073fbd92bdc24734b1f56fb'), + ParagonIE_Sodium_Core_Util::hex2bin('a828a18d6a964497ac6e7e53c5f55c73') + )); + $m = ParagonIE_Sodium_Core_Util::hex2bin('b165617ed04ab738afb2612c6d18a1ec'); + $state->update($m); + $s = $state->getState(); + $expected = array( + ParagonIE_Sodium_Core_Util::hex2bin('e6bc643bae82dfa3d991b1b323839dcd'), + ParagonIE_Sodium_Core_Util::hex2bin('648578232ba0f2f0a3677f617dc052c3'), + ParagonIE_Sodium_Core_Util::hex2bin('ea788e0e572044a46059212dd007a789'), + ParagonIE_Sodium_Core_Util::hex2bin('2f1498ae19b80da13fba698f088a8590'), + ParagonIE_Sodium_Core_Util::hex2bin('a54c2ee95e8c2a2c3dae2ec743ae6b86'), + ParagonIE_Sodium_Core_Util::hex2bin('a3240fceb68e32d5d114df1b5363ab67') + ); + $this->assertSame($s, $expected); + } + + /** + * @return array[] + * + * name, key, nonce, tag, ciphertext, plaintext, aad, expect_fail? + */ + public function aegis128lVectors() + { + return array( + array( + 'AEGIS-128L test vector 1', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '25835bfbb21632176cf03840687cb968cace4617af1bd0f7d064c639a5c79ee4', + 'c1c0e58bd913006feba00f4b3cc3594e', + '00000000000000000000000000000000', + '', + false + ), + array( + 'AEGIS-128L test vector 2', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '1360dc9db8ae42455f6e5b6a9d488ea4f2184c4e12120249335c4ee84bafe25d', + '', + '', + '', + false + ), + array( + 'AEGIS-128L test vector 3', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '022cb796fe7e0ae1197525ff67e309484cfbab6528ddef89f17d74ef8ecd82b3', + '79d94593d8c2119d7e8fd9b8fc77845c5c077a05b2528b6ac54b563aed8efe84', + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + '0001020304050607', + false + ), + array( + 'AEGIS-128L test vector 4', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac', + '79d94593d8c2119d7e8fd9b8fc77', + '000102030405060708090a0b0c0d', + '0001020304050607', + false + ), + array( + 'AEGIS-128L test vector 5', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + 'b91e2947a33da8bee89b6794e647baf0fc835ff574aca3fc27c33be0db2aff98', + 'b31052ad1cca4e291abcf2df3502e6bdb1bfd6db36798be3607b1f94d34478aa7ede7f7a990fec10', + '101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637', + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829', + false + ), + array( + 'AEGIS-128L test vector 6', + '10000200000000000000000000000000', + '10010000000000000000000000000000', + '86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac', + '79d94593d8c2119d7e8fd9b8fc77', + '', + '0001020304050607', + true + ), + array( + 'AEGIS-128L test vector 7', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac', + '79d94593d8c2119d7e8fd9b8fc78', + '', + '0001020304050607', + true + ), + array( + 'AEGIS-128L test vector 8', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac', + '79d94593d8c2119d7e8fd9b8fc77', + '', + '0001020304050608', + true + ), + array( + 'AEGIS-128L test vector 9', + '10010000000000000000000000000000', + '10000200000000000000000000000000', + '86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ad', + '79d94593d8c2119d7e8fd9b8fc77', + '', + '0001020304050607', + true + ), + ); + } + + /** + * @return array[] + * + * name, key, nonce, tag, ciphertext, plaintext, aad, expect_fail? + */ + public function aegis256Vectors() + { + return array( + array( + 'AEGIS-256 test vector 1', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '1181a1d18091082bf0266f66297d167d2e68b845f61a3b0527d31fc7b7b89f13', + '754fc3d8c973246dcc6d741412a4b236', + '00000000000000000000000000000000', + '', + false + ), + array( + 'AEGIS-256 test vector 2', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '6a348c930adbd654896e1666aad67de989ea75ebaa2b82fb588977b1ffec864a', + '', + '', + '', + false + ), + array( + 'AEGIS-256 test vector 3', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + 'b7d28d0c3c0ebd409fd22b44160503073a547412da0854bfb9723020dab8da1a', + 'f373079ed84b2709faee373584585d60accd191db310ef5d8b11833df9dec711', + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + '0001020304050607', + false + ), + array( + 'AEGIS-256 test vector 4', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9', + 'f373079ed84b2709faee37358458', + '000102030405060708090a0b0c0d', + '0001020304050607', + false + ), + array( + 'AEGIS-256 test vector 5', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + 'a3aca270c006094d71c20e6910b5161c0826df233d08919a566ec2c05990f734', + '57754a7d09963e7c787583a2e7b859bb24fa1e04d49fd550b2511a358e3bca252a9b1b8b30cc4a67', + '101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637', + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829', + false + ), + array( + 'AEGIS-256 test vector 6', + '1000020000000000000000000000000000000000000000000000000000000000', + '1001000000000000000000000000000000000000000000000000000000000000', + '8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9', + 'f373079ed84b2709faee37358458', + '', + '0001020304050607', + true + ), + array( + 'AEGIS-256 test vector 7', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9', + 'f373079ed84b2709faee37358459', + '', + '0001020304050607', + true + ), + array( + 'AEGIS-256 test vector 8', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9', + 'f373079ed84b2709faee37358458', + '', + '0001020304050608', + true + ), + array( + 'AEGIS-256 test vector 9', + '1001000000000000000000000000000000000000000000000000000000000000', + '1000020000000000000000000000000000000000000000000000000000000000', + '8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2da', + 'f373079ed84b2709faee37358458', + '', + '0001020304050607', + true + ) + ); + } + + /** + * @dataProvider aegis128lVectors + * @param string $key_hex + * @param string $nonce_hex + * @param string $expected_tag_hex + * @param string $expected_ct_hex + * @param string $msg_hex + * @param string $ad_hex + * @param bool $expect_fail + * @return void + * @throws SodiumException + */ + public function testAegis128lVectors( + $name, + $key_hex, + $nonce_hex, + $expected_tag_hex, + $expected_ct_hex, + $msg_hex = '', + $ad_hex = '', + $expect_fail = false + ) { + $key = ParagonIE_Sodium_Core_Util::hex2bin($key_hex); + $nonce = ParagonIE_Sodium_Core_Util::hex2bin($nonce_hex); + $expTag = ParagonIE_Sodium_Core_Util::hex2bin($expected_tag_hex); + $expCt = ParagonIE_Sodium_Core_Util::hex2bin($expected_ct_hex); + $ad = ParagonIE_Sodium_Core_Util::hex2bin($ad_hex); + if ($expect_fail) { + $failed = false; + try { + ParagonIE_Sodium_Core_AEGIS128L::decrypt($expCt, $expTag, $ad, $key, $nonce); + } catch (SodiumException $ex) { + $failed = true; + } + $this->assertTrue($failed, 'Expected decryption to fail but it did not'); + return; + } + $msg = ParagonIE_Sodium_Core_Util::hex2bin($msg_hex); + list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS128L::encrypt($msg, $ad, $key, $nonce); + + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($expCt), + ParagonIE_Sodium_Core_Util::bin2hex($ct), + $name + ); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($expTag), + ParagonIE_Sodium_Core_Util::bin2hex($tag), + $name + ); + $this->assertSame($expCt, $ct, $name); + $this->assertSame($expTag, $tag, $name); + $got_pt = ParagonIE_Sodium_Core_AEGIS128L::decrypt($expCt, $expTag, $ad, $key, $nonce); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($got_pt), + $msg_hex, + $name + ); + $this->assertSame($got_pt, $msg, $name); + } + + /** + * @dataProvider aegis256Vectors + * @param string $key_hex + * @param string $nonce_hex + * @param string $expected_tag_hex + * @param string $expected_ct_hex + * @param string $msg_hex + * @param string $ad_hex + * @param bool $expect_fail + * @return void + * @throws SodiumException + */ + public function testAegis256Vectors( + $name, + $key_hex, + $nonce_hex, + $expected_tag_hex, + $expected_ct_hex, + $msg_hex = '', + $ad_hex = '', + $expect_fail = false + ) { + $key = ParagonIE_Sodium_Core_Util::hex2bin($key_hex); + $nonce = ParagonIE_Sodium_Core_Util::hex2bin($nonce_hex); + $expTag = ParagonIE_Sodium_Core_Util::hex2bin($expected_tag_hex); + $expCt = ParagonIE_Sodium_Core_Util::hex2bin($expected_ct_hex); + $ad = ParagonIE_Sodium_Core_Util::hex2bin($ad_hex); + if ($expect_fail) { + $failed = false; + try { + ParagonIE_Sodium_Core_AEGIS256::decrypt($expCt, $expTag, $ad, $key, $nonce); + } catch (SodiumException $ex) { + $failed = true; + } + $this->assertTrue($failed, 'Expected decryption to fail but it did not'); + return; + } + $msg = ParagonIE_Sodium_Core_Util::hex2bin($msg_hex); + list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS256::encrypt($msg, $ad, $key, $nonce); + + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($expCt), + ParagonIE_Sodium_Core_Util::bin2hex($ct), + $name + ); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($expTag), + ParagonIE_Sodium_Core_Util::bin2hex($tag), + $name + ); + $this->assertSame($expCt, $ct, $name); + $this->assertSame($expTag, $tag, $name); + $got_pt = ParagonIE_Sodium_Core_AEGIS256::decrypt($expCt, $expTag, $ad, $key, $nonce); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($got_pt), + $msg_hex, + $name + ); + $this->assertSame($got_pt, $msg, $name); + } + +} \ No newline at end of file diff --git a/tests/unit/AESTest.php b/tests/unit/AESTest.php index b37c78d9..37fd027b 100644 --- a/tests/unit/AESTest.php +++ b/tests/unit/AESTest.php @@ -319,6 +319,18 @@ public function testSkeyExpand() } } + public function testAesRound() + { + $in = ParagonIE_Sodium_Core_Util::hex2bin('000102030405060708090a0b0c0d0e0f'); + $rk = ParagonIE_Sodium_Core_Util::hex2bin('101112131415161718191a1b1c1d1e1f'); + $this->assertSame( + '7a7b4e5638782546a8c0477a3b813f43', + ParagonIE_Sodium_Core_Util::bin2hex( + ParagonIE_Sodium_Core_AES::aesRound($in, $rk) + ) + ); + } + /** * @dataProvider aes128ecbProvider * @covers ParagonIE_Sodium_Core_AES::encryptBlockECB diff --git a/tests/unit/UtilTest.php b/tests/unit/UtilTest.php index 845e3010..2155833e 100644 --- a/tests/unit/UtilTest.php +++ b/tests/unit/UtilTest.php @@ -10,6 +10,23 @@ public function before() ParagonIE_Sodium_Compat::$disableFallbackForUnitTests = true; } + public function testAndString() + { + $x = "\x01\x02\x03\x04"; + $y = "\xff\xff\xff\xff"; + $z = "\xcc\x8a\xcc\x00"; + + $this->assertSame($x, ParagonIE_Sodium_Core_Util::andStrings($x, $y)); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($z), + ParagonIE_Sodium_Core_Util::bin2hex(ParagonIE_Sodium_Core_Util::andStrings($y, $z)) + ); + $this->assertSame( + '00020000', + ParagonIE_Sodium_Core_Util::bin2hex(ParagonIE_Sodium_Core_Util::andStrings($x, $z)) + ); + } + public function testAbs() { $this->assertEquals(0, ParagonIE_Sodium_Core_Util::abs(0));