<?php
declare(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;
}
}
|