From a4ae7675f87094bd71288eb4479123be6e2b6475 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Wed, 17 Apr 2024 19:21:59 -0400 Subject: [PATCH 1/5] Add boilerplate to guard against autoloader issues --- src/Core/AEGIS/State128L.php | 5 +++++ src/Core/AEGIS/State256.php | 4 ++++ src/Core/AES.php | 4 ++++ src/Core/AES/Expanded.php | 4 ++++ src/Core/AES/KeySchedule.php | 4 ++++ 5 files changed, 21 insertions(+) diff --git a/src/Core/AEGIS/State128L.php b/src/Core/AEGIS/State128L.php index 0bbf99d2..f6d1ab97 100644 --- a/src/Core/AEGIS/State128L.php +++ b/src/Core/AEGIS/State128L.php @@ -1,4 +1,9 @@ Date: Fri, 19 Apr 2024 04:41:44 -0400 Subject: [PATCH 2/5] Add to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 88e76292..d55e50dc 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,10 @@ insightful technical information you may find helpful. * `crypto_sign()` * `crypto_sign_open()` * PECL Libsodium Features + * `crypto_aead_aegis128l_encrypt()` + * `crypto_aead_aegis128l_decrypt()` + * `crypto_aead_aegis256_encrypt()` + * `crypto_aead_aegis256_decrypt()` * `crypto_aead_aes256gcm_encrypt()` * `crypto_aead_aes256gcm_decrypt()` * `crypto_aead_chacha20poly1305_encrypt()` From e9469567d828117087c8dd095551e5d51d293cbd Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Fri, 19 Apr 2024 04:43:10 -0400 Subject: [PATCH 3/5] The \Sodium\ namespace is no longer as prevalent --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d55e50dc..60aa447f 100644 --- a/README.md +++ b/README.md @@ -347,13 +347,13 @@ insightful technical information you may find helpful. ### Features Excluded from this Polyfill -* `\Sodium\memzero()` - Although we expose this API endpoint, we can't reliably +* `sodium_memzero()` - Although we expose this API endpoint, we can't reliably zero buffers from PHP. If you have the PHP extension installed, sodium_compat will use the native implementation to zero out the string provided. Otherwise it will throw a `SodiumException`. -* `\Sodium\crypto_pwhash()` - It's not feasible to polyfill scrypt or Argon2 +* `sodium_crypto_pwhash()` - It's not feasible to polyfill scrypt or Argon2 into PHP and get reasonable performance. Users would feel motivated to select parameters that downgrade security to avoid denial of service (DoS) attacks. @@ -365,6 +365,8 @@ insightful technical information you may find helpful. To detect support for Argon2i at runtime, use `ParagonIE_Sodium_Compat::crypto_pwhash_is_available()`, which returns a boolean value (`TRUE` or `FALSE`). +* Libsodium's HKDF API (`crypto_kdf_hkdf_*()`) is not included because PHP has + its own [HMAC features](https://php.met/hash_hmac) amd it was not deemed necessary. ### PHPCompatibility Ruleset From 93775993be04537a3879064f269bc8962dfe9856 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Fri, 19 Apr 2024 04:53:19 -0400 Subject: [PATCH 4/5] Create double AES-round API to improve throughput --- src/Core/AES.php | 50 ++++++++++++++++++++++++++++++++++++++++++ tests/unit/AESTest.php | 30 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/Core/AES.php b/src/Core/AES.php index 411fc308..d86cff1a 100644 --- a/src/Core/AES.php +++ b/src/Core/AES.php @@ -445,6 +445,56 @@ public static function aesRound($x, $y) self::store32_le($q[6]); } + /** + * 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 + * @return string[] + */ + public static function doubleRound($b0, $rk0, $b1, $rk1) + { + $q = ParagonIE_Sodium_Core_AES_Block::init(); + // First block + $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)); + // Second block + $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(); + // First round key + $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)); + // Second round key + $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)); + + $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 array( + 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 diff --git a/tests/unit/AESTest.php b/tests/unit/AESTest.php index 37fd027b..4371d790 100644 --- a/tests/unit/AESTest.php +++ b/tests/unit/AESTest.php @@ -331,6 +331,36 @@ public function testAesRound() ); } + public function testAesDoubleRound() + { + $in = ParagonIE_Sodium_Core_Util::hex2bin('000102030405060708090a0b0c0d0e0f'); + $rk = ParagonIE_Sodium_Core_Util::hex2bin('101112131415161718191a1b1c1d1e1f'); + $this->assertSame( + '7a7b4e5638782546a8c0477a3b813f437a7b4e5638782546a8c0477a3b813f43', + ParagonIE_Sodium_Core_Util::bin2hex( + implode('', ParagonIE_Sodium_Core_AES::doubleRound($in, $rk, $in, $rk)) + ) + ); + + // Let's randomize this to test equivalence. + $in0 = random_bytes(16); + $in1 = random_bytes(16); + $rk0 = random_bytes(16); + $rk1 = random_bytes(16); + + $c0 = ParagonIE_Sodium_Core_AES::aesRound($in0, $rk0); + $c1 = ParagonIE_Sodium_Core_AES::aesRound($in1, $rk1); + list($c2, $c3) = ParagonIE_Sodium_Core_AES::doubleRound($in0, $rk0, $in1, $rk1); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($c0), + ParagonIE_Sodium_Core_Util::bin2hex($c2) + ); + $this->assertSame( + ParagonIE_Sodium_Core_Util::bin2hex($c1), + ParagonIE_Sodium_Core_Util::bin2hex($c3) + ); + } + /** * @dataProvider aes128ecbProvider * @covers ParagonIE_Sodium_Core_AES::encryptBlockECB From eef0ebec0db8a931310b2b1c7f3347c9c637e820 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Fri, 19 Apr 2024 04:59:07 -0400 Subject: [PATCH 5/5] Use double-round function for better performance. Since our bitsliced AES process two blocks at once, it's only logical to reduce the number of calls to this function. --- src/Core/AEGIS/State128L.php | 28 ++++++++++++++++------------ src/Core/AEGIS/State256.php | 20 ++++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Core/AEGIS/State128L.php b/src/Core/AEGIS/State128L.php index f6d1ab97..9decd2b4 100644 --- a/src/Core/AEGIS/State128L.php +++ b/src/Core/AEGIS/State128L.php @@ -242,20 +242,24 @@ public function update($m0, $m1) S'6 = AESRound(S5, S6) S'7 = AESRound(S6, S7) */ - $s_0 = ParagonIE_Sodium_Core_AES::aesRound( - $this->state[7], - $this->state[0] ^ $m0 + list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[7], $this->state[0] ^ $m0, + $this->state[0], $this->state[1] ); - $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 + + list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[1], $this->state[2], + $this->state[2], $this->state[3] + ); + + list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[3], $this->state[4] ^ $m1, + $this->state[4], $this->state[5] + ); + list($s_6, $s_7) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[5], $this->state[6], + $this->state[6], $this->state[7] ); - $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 diff --git a/src/Core/AEGIS/State256.php b/src/Core/AEGIS/State256.php index c216fd83..6f88b828 100644 --- a/src/Core/AEGIS/State256.php +++ b/src/Core/AEGIS/State256.php @@ -207,15 +207,19 @@ public function update($m) S'4 = AESRound(S3, S4) S'5 = AESRound(S4, S5) */ - $s_0 = ParagonIE_Sodium_Core_AES::aesRound( - $this->state[5], - $this->state[0] ^ $m + list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[5],$this->state[0] ^ $m, + $this->state[0], $this->state[1] + ); + + list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[1], $this->state[2], + $this->state[2], $this->state[3] + ); + list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[3], $this->state[4], + $this->state[4], $this->state[5] ); - $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