| 
<?phpdeclare(strict_types=1);
 namespace ParagonIE\Sapient;
 
 use ParagonIE\Sapient\CryptographyKeys\{
 SealingPublicKey,
 SealingSecretKey,
 SharedEncryptionKey
 };
 use ParagonIE\Sapient\Exception\InvalidMessageException;
 
 /**
 * Class Simple
 * @package ParagonIE\Sapient
 */
 abstract class Simple
 {
 /**
 * Simple authenticated encryption
 * XChaCha20-Poly1305
 *
 * @param string $plaintext
 * @param SharedEncryptionKey $key
 * @return string
 */
 public static function encrypt(
 string $plaintext,
 SharedEncryptionKey $key
 ): string {
 $nonce = random_bytes(\ParagonIE_Sodium_Compat::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
 return $nonce .
 \ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt(
 $plaintext,
 $nonce,
 $nonce,
 $key->getString(true)
 );
 }
 
 /**
 * Simple authenticated decryption
 * XChaCha20-Poly1305
 *
 * @param string $ciphertext
 * @param SharedEncryptionKey $key
 * @return string
 * @throws InvalidMessageException
 */
 public static function decrypt(
 string $ciphertext,
 SharedEncryptionKey $key
 ): string {
 $nonce = \ParagonIE_Sodium_Core_Util::substr($ciphertext, 0, 24);
 $result = \ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt(
 \ParagonIE_Sodium_Core_Util::substr($ciphertext, 24),
 $nonce,
 $nonce,
 $key->getString(true)
 );
 if (!\is_string($result)) {
 throw new InvalidMessageException('Message authentication failed.');
 }
 return $result;
 }
 
 /**
 * Like libsodium's crypto_kx() but supports an arbitrary output length
 * in the range (16 <= N <= 64).
 *
 * @param SealingSecretKey $secretKey
 * @param SealingPublicKey $publicKey
 * @param bool $serverSide
 * @param int $outputLength
 * @return string
 */
 public static function keyExchange(
 SealingSecretKey $secretKey,
 SealingPublicKey $publicKey,
 bool $serverSide,
 int $outputLength = 32
 ): string {
 if ($serverSide) {
 $suffix = $publicKey->getString(true) .
 $secretKey->getPublickey()->getString(true);
 } else {
 $suffix = $secretKey->getPublickey()->getString(true) .
 $publicKey->getString(true);
 }
 return \ParagonIE_Sodium_Compat::crypto_generichash(
 \ParagonIE_Sodium_Compat::crypto_scalarmult(
 $secretKey->getString(true),
 $publicKey->getString(true)
 ) . $suffix,
 '',
 $outputLength
 );
 }
 
 /**
 * Encrypt a message with a public key, so that it can only be decrypted
 * with the corresponding secret key.
 *
 * @param string $plaintext
 * @param SealingPublicKey $publicKey
 * @return string
 */
 public static function seal(
 string $plaintext,
 SealingPublicKey $publicKey
 ): string {
 $ephemeralSecret = SealingSecretKey::generate();
 $sharedSecret = static::keyExchange(
 $ephemeralSecret,
 $publicKey,
 false,
 56
 );
 $ephemeralPublicKey = $ephemeralSecret->getPublickey()->getString(true);
 $sharedKey = \ParagonIE_Sodium_Core_Util::substr($sharedSecret, 0, 32);
 $nonce = \ParagonIE_Sodium_Core_Util::substr($sharedSecret, 32, 24);
 try {
 \ParagonIE_Sodium_Compat::memzero($sharedSecret);
 } catch (\Throwable $ex) {
 }
 
 return $ephemeralPublicKey. \ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt(
 $plaintext,
 $ephemeralPublicKey,
 $nonce,
 $sharedKey
 );
 }
 
 /**
 * Decrypt a message with your secret key.
 *
 * @param string $ciphertext
 * @param SealingSecretKey $secretKey
 * @return string
 * @throws InvalidMessageException
 */
 public static function unseal(
 string $ciphertext,
 SealingSecretKey $secretKey
 ): string {
 $ephemeralPublicKey = \ParagonIE_Sodium_Core_Util::substr($ciphertext, 0, 32);
 
 $sharedSecret = static::keyExchange(
 $secretKey,
 new SealingPublicKey($ephemeralPublicKey),
 true,
 56
 );
 $sharedKey = \ParagonIE_Sodium_Core_Util::substr($sharedSecret, 0, 32);
 $nonce = \ParagonIE_Sodium_Core_Util::substr($sharedSecret, 32, 24);
 try {
 \ParagonIE_Sodium_Compat::memzero($sharedSecret);
 } catch (\Throwable $ex) {
 }
 
 $plaintext = \ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt(
 \ParagonIE_Sodium_Core_Util::substr($ciphertext, 32),
 $ephemeralPublicKey,
 $nonce,
 $sharedKey
 );
 if (!\is_string($plaintext)) {
 throw new InvalidMessageException('Incorrect message authentication tag');
 }
 return $plaintext;
 }
 }
 
 |