From a7b337de892329fa3de20bb5f5ce8939aefd0647 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Wed, 17 Apr 2024 03:44:40 -0400 Subject: [PATCH] Expose AEGIS through public API and polyfill --- lib/php72compat.php | 98 ++++++++++++++++- src/Compat.php | 220 +++++++++++++++++++++++++++++++++++++++ tests/unit/AEGISTest.php | 23 +++- 3 files changed, 336 insertions(+), 5 deletions(-) diff --git a/lib/php72compat.php b/lib/php72compat.php index e949dbdc..ee81d4e7 100644 --- a/lib/php72compat.php +++ b/lib/php72compat.php @@ -14,14 +14,22 @@ 'BASE64_VARIANT_ORIGINAL_NO_PADDING', 'BASE64_VARIANT_URLSAFE', 'BASE64_VARIANT_URLSAFE_NO_PADDING', - 'CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_ABYTES', + 'CRYPTO_AEAD_AESGIS128L_KEYBYTES', + 'CRYPTO_AEAD_AESGIS128L_NSECBYTES', + 'CRYPTO_AEAD_AESGIS128L_NPUBBYTES', + 'CRYPTO_AEAD_AESGIS128L_ABYTES', + 'CRYPTO_AEAD_AESGIS256_KEYBYTES', + 'CRYPTO_AEAD_AESGIS256_NSECBYTES', + 'CRYPTO_AEAD_AESGIS256_NPUBBYTES', + 'CRYPTO_AEAD_AESGIS256_ABYTES', 'CRYPTO_AEAD_AES256GCM_KEYBYTES', 'CRYPTO_AEAD_AES256GCM_NSECBYTES', 'CRYPTO_AEAD_AES256GCM_NPUBBYTES', 'CRYPTO_AEAD_AES256GCM_ABYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_ABYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES', @@ -176,6 +184,88 @@ function sodium_compare($string1, $string2) return ParagonIE_Sodium_Compat::compare($string1, $string2); } } +if (!is_callable('sodium_crypto_aead_aegis128l_decrypt')) { + /** + * @see ParagonIE_Sodium_Compat::crypto_aead_aegis128l_decrypt() + * @param string $ciphertext + * @param string $additional_data + * @param string $nonce + * @param string $key + * @return string + * @throws SodiumException + */ + function sodium_crypto_aead_aegis128l_decrypt($ciphertext, $additional_data, $nonce, $key) + { + return ParagonIE_Sodium_Compat::crypto_aead_aegis128l_decrypt( + $ciphertext, + $additional_data, + $nonce, + $key + ); + } +} +if (!is_callable('sodium_crypto_aead_aegis128l_encrypt')) { + /** + * @see ParagonIE_Sodium_Compat::crypto_aead_aegis128l_encrypt() + * @param string $message + * @param string $additional_data + * @param string $nonce + * @param string $key + * @return string + * @throws SodiumException + * @throws TypeError + */ + function sodium_crypto_aead_aegis128l_encrypt($message, $additional_data, $nonce, $key) + { + return ParagonIE_Sodium_Compat::crypto_aead_aegis128l_encrypt( + $message, + $additional_data, + $nonce, + $key + ); + } +} +if (!is_callable('sodium_crypto_aead_aegis256_decrypt')) { + /** + * @see ParagonIE_Sodium_Compat::crypto_aead_aegis256_encrypt() + * @param string $ciphertext + * @param string $additional_data + * @param string $nonce + * @param string $key + * @return string + * @throws SodiumException + */ + function sodium_crypto_aead_aegis256_decrypt($ciphertext, $additional_data, $nonce, $key) + { + return ParagonIE_Sodium_Compat::crypto_aead_aegis256_decrypt( + $ciphertext, + $additional_data, + $nonce, + $key + ); + } +} +if (!is_callable('sodium_crypto_aead_aegis256_encrypt')) { + /** + * @see ParagonIE_Sodium_Compat::crypto_aead_aegis256_encrypt() + * @param string $message + * @param string $additional_data + * @param string $nonce + * @param string $key + * @return string + * @throws SodiumException + * @throws TypeError + */ + function sodium_crypto_aead_aegis256_encrypt($message, $additional_data, $nonce, $key) + { + return ParagonIE_Sodium_Compat::crypto_aead_aegis256_encrypt( + $message, + $additional_data, + $nonce, + $key + ); + } +} if (!is_callable('sodium_crypto_aead_aes256gcm_decrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt() diff --git a/src/Compat.php b/src/Compat.php index 3afe97c0..a675aa57 100644 --- a/src/Compat.php +++ b/src/Compat.php @@ -59,6 +59,14 @@ class ParagonIE_Sodium_Compat const CRYPTO_AEAD_AES256GCM_NSECBYTES = 0; const CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12; const CRYPTO_AEAD_AES256GCM_ABYTES = 16; + const CRYPTO_AEAD_AEGIS128L_KEYBYTES = 16; + const CRYPTO_AEAD_AEGIS128L_NSECBYTES = 0; + const CRYPTO_AEAD_AEGIS128L_NPUBBYTES = 16; + const CRYPTO_AEAD_AEGIS128L_ABYTES = 32; + const CRYPTO_AEAD_AEGIS256_KEYBYTES = 32; + const CRYPTO_AEAD_AEGIS256_NSECBYTES = 0; + const CRYPTO_AEAD_AEGIS256_NPUBBYTES = 32; + const CRYPTO_AEAD_AEGIS256_ABYTES = 32; const CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32; const CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0; const CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8; @@ -299,6 +307,218 @@ public static function compare($left, $right) return ParagonIE_Sodium_Core_Util::compare($left, $right); } + /** + * Authenticated Encryption with Associated Data: Decryption + * + * Algorithm: + * AEGIS-128L + * + * @param string $ciphertext Encrypted message (with MAC appended) + * @param string $assocData Authenticated Associated Data (unencrypted) + * @param string $nonce Number to be used only Once; must be 32 bytes + * @param string $key Encryption key + * + * @return string The original plaintext message + * @throws SodiumException + * @throws TypeError + * @psalm-suppress MixedArgument + * @psalm-suppress MixedInferredReturnType + * @psalm-suppress MixedReturnStatement + */ + public static function crypto_aead_aegis128l_decrypt( + $ciphertext = '', + $assocData = '', + $nonce = '', + $key = '' + ) { + ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4); + + /* Input validation: */ + if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS128L_NPUBBYTES) { + throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS_128L_NPUBBYTES long'); + } + if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS128L_KEYBYTES) { + throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long'); + } + $ct_length = ParagonIE_Sodium_Core_Util::strlen($ciphertext); + if ($ct_length < self::CRYPTO_AEAD_AEGIS128L_ABYTES) { + throw new SodiumException('Message must be at least CRYPTO_AEAD_AEGIS128L_ABYTES long'); + } + + $ct = ParagonIE_Sodium_Core_Util::substr( + $ciphertext, + 0, + $ct_length - self::CRYPTO_AEAD_AEGIS128L_ABYTES + ); + $tag = ParagonIE_Sodium_Core_Util::substr( + $ciphertext, + $ct_length - self::CRYPTO_AEAD_AEGIS128L_ABYTES, + self::CRYPTO_AEAD_AEGIS128L_ABYTES + ); + return ParagonIE_Sodium_Core_AEGIS128L::decrypt($ct, $tag, $assocData, $key, $nonce); + } + + /** + * Authenticated Encryption with Associated Data: Encryption + * + * Algorithm: + * AEGIS-128L + * + * @param string $plaintext Message to be encrypted + * @param string $assocData Authenticated Associated Data (unencrypted) + * @param string $nonce Number to be used only Once; must be 32 bytes + * @param string $key Encryption key + * + * @return string Ciphertext with 32-byte authentication tag appended + * @throws SodiumException + * @throws TypeError + * @psalm-suppress MixedArgument + */ + public static function crypto_aead_aegis128l_encrypt( + $plaintext = '', + $assocData = '', + $nonce = '', + $key = '' + ) { + ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4); + + /* Input validation: */ + if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS128L_NPUBBYTES) { + throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long'); + } + if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS128L_KEYBYTES) { + throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long'); + } + + list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS128L::encrypt($plaintext, $assocData, $key, $nonce); + return $ct . $tag; + } + + /** + * Return a secure random key for use with the AEGIS-128L + * symmetric AEAD interface. + * + * @return string + * @throws Exception + * @throws Error + */ + public static function crypto_aead_aegis128l_keygen() + { + return random_bytes(self::CRYPTO_AEAD_AEGIS128L_KEYBYTES); + } + + /** + * Authenticated Encryption with Associated Data: Decryption + * + * Algorithm: + * AEGIS-256 + * + * @param string $ciphertext Encrypted message (with MAC appended) + * @param string $assocData Authenticated Associated Data (unencrypted) + * @param string $nonce Number to be used only Once; must be 32 bytes + * @param string $key Encryption key + * + * @return string The original plaintext message + * @throws SodiumException + * @throws TypeError + * @psalm-suppress MixedArgument + * @psalm-suppress MixedInferredReturnType + * @psalm-suppress MixedReturnStatement + */ + public static function crypto_aead_aegis256_decrypt( + $ciphertext = '', + $assocData = '', + $nonce = '', + $key = '' + ) { + ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4); + + /* Input validation: */ + if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS256_NPUBBYTES) { + throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS256_NPUBBYTES long'); + } + if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS256_KEYBYTES) { + throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS256_KEYBYTES long'); + } + $ct_length = ParagonIE_Sodium_Core_Util::strlen($ciphertext); + if ($ct_length < self::CRYPTO_AEAD_AEGIS256_ABYTES) { + throw new SodiumException('Message must be at least CRYPTO_AEAD_AEGIS256_ABYTES long'); + } + + $ct = ParagonIE_Sodium_Core_Util::substr( + $ciphertext, + 0, + $ct_length - self::CRYPTO_AEAD_AEGIS256_ABYTES + ); + $tag = ParagonIE_Sodium_Core_Util::substr( + $ciphertext, + $ct_length - self::CRYPTO_AEAD_AEGIS256_ABYTES, + self::CRYPTO_AEAD_AEGIS256_ABYTES + ); + return ParagonIE_Sodium_Core_AEGIS256::decrypt($ct, $tag, $assocData, $key, $nonce); + } + + /** + * Authenticated Encryption with Associated Data: Encryption + * + * Algorithm: + * AEGIS-256 + * + * @param string $plaintext Message to be encrypted + * @param string $assocData Authenticated Associated Data (unencrypted) + * @param string $nonce Number to be used only Once; must be 32 bytes + * @param string $key Encryption key + * + * @return string Ciphertext with 32-byte authentication tag appended + * @throws SodiumException + * @throws TypeError + * @psalm-suppress MixedArgument + */ + public static function crypto_aead_aegis256_encrypt( + $plaintext = '', + $assocData = '', + $nonce = '', + $key = '' + ) { + ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4); + + /* Input validation: */ + if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS256_NPUBBYTES) { + throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long'); + } + if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS256_KEYBYTES) { + throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long'); + } + + list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS256::encrypt($plaintext, $assocData, $key, $nonce); + return $ct . $tag; + } + + /** + * Return a secure random key for use with the AEGIS-256 + * symmetric AEAD interface. + * + * @return string + * @throws Exception + * @throws Error + */ + public static function crypto_aead_aegis256_keygen() + { + return random_bytes(self::CRYPTO_AEAD_AEGIS256_KEYBYTES); + } + /** * Is AES-256-GCM even available to use? * diff --git a/tests/unit/AEGISTest.php b/tests/unit/AEGISTest.php index 2af92d03..c403ca3a 100644 --- a/tests/unit/AEGISTest.php +++ b/tests/unit/AEGISTest.php @@ -387,4 +387,25 @@ public function testAegis256Vectors( $this->assertSame($got_pt, $msg, $name); } -} \ No newline at end of file + public function testPublicAegis128l() + { + $msg = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::randombytes_uniform(999) + 1); + $nonce = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::CRYPTO_AEAD_AEGIS128L_NPUBBYTES); + $ad = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::randombytes_uniform(999) + 1); + $key = ParagonIE_Sodium_Compat::crypto_aead_aegis128l_keygen(); + $ciphertext = ParagonIE_Sodium_Compat::crypto_aead_aegis128l_encrypt($msg, $ad, $nonce, $key); + $msg2 = ParagonIE_Sodium_Compat::crypto_aead_aegis128l_decrypt($ciphertext, $ad, $nonce, $key); + $this->assertSame($msg, $msg2); + } + + public function testPublicAegis256() + { + $msg = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::randombytes_uniform(999) + 1); + $nonce = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::CRYPTO_AEAD_AEGIS256_NPUBBYTES); + $ad = ParagonIE_Sodium_Compat::randombytes_buf(ParagonIE_Sodium_Compat::randombytes_uniform(999) + 1); + $key = ParagonIE_Sodium_Compat::crypto_aead_aegis256_keygen(); + $ciphertext = ParagonIE_Sodium_Compat::crypto_aead_aegis256_encrypt($msg, $ad, $nonce, $key); + $msg2 = ParagonIE_Sodium_Compat::crypto_aead_aegis256_decrypt($ciphertext, $ad, $nonce, $key); + $this->assertSame($msg, $msg2); + } +}