409 lines
11 KiB
PHP
409 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* very simple asymmetric encryption
|
|
* Better use:
|
|
* https://paragonie.com/project/halite
|
|
* https://github.com/paragonie/halite
|
|
*
|
|
* current code is just to encrypt and decrypt
|
|
*
|
|
* must use a valid encryption key created with
|
|
* Secruty\CreateKey class
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace CoreLibs\Security;
|
|
|
|
use CoreLibs\Security\CreateKey;
|
|
use SodiumException;
|
|
|
|
class AsymmetricAnonymousEncryption
|
|
{
|
|
/** @var AsymmetricAnonymousEncryption self instance */
|
|
private static AsymmetricAnonymousEncryption $instance;
|
|
|
|
/** @var ?string key pair which holds secret and public key, needed for encryption */
|
|
private ?string $key_pair = null;
|
|
/** @var ?string public key, needed for decryption
|
|
* if not set but key_pair set, this will be extracted from key pair */
|
|
private ?string $public_key = null;
|
|
|
|
/**
|
|
* init class
|
|
* if key not passed, key must be set with createKey
|
|
*
|
|
* @param string|null $key_pair
|
|
* @param string|null $public_key
|
|
*/
|
|
public function __construct(
|
|
#[\SensitiveParameter]
|
|
string|null $key_pair = null,
|
|
string|null $public_key = null
|
|
) {
|
|
if ($public_key !== null) {
|
|
$this->setPublicKey($public_key);
|
|
}
|
|
if ($key_pair !== null) {
|
|
$this->setKeyPair($key_pair);
|
|
if (empty($public_key)) {
|
|
$public_key = CreateKey::getPublicKey($key_pair);
|
|
$this->setPublicKey($public_key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the singleton self object.
|
|
* For function wrapper use
|
|
*
|
|
* @param string|null $key_pair
|
|
* @param string|null $public_key
|
|
* @return AsymmetricAnonymousEncryption object
|
|
*/
|
|
public static function getInstance(
|
|
#[\SensitiveParameter]
|
|
string|null $key_pair = null,
|
|
string|null $public_key = null
|
|
): self {
|
|
// new if no instsance or key is different
|
|
if (
|
|
empty(self::$instance) ||
|
|
self::$instance->key_pair != $key_pair ||
|
|
self::$instance->public_key != $public_key
|
|
) {
|
|
self::$instance = new self($key_pair, $public_key);
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* clean up
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if (empty($this->key_pair)) {
|
|
return;
|
|
}
|
|
try {
|
|
// would set it to null, but we we do not want to make key null
|
|
sodium_memzero($this->key_pair);
|
|
return;
|
|
} catch (SodiumException) {
|
|
// empty catch
|
|
}
|
|
if (is_null($this->key_pair)) {
|
|
return;
|
|
}
|
|
$zero = str_repeat("\0", mb_strlen($this->key_pair, '8bit'));
|
|
$this->key_pair = $this->key_pair ^ (
|
|
$zero ^ $this->key_pair
|
|
);
|
|
unset($zero);
|
|
unset($this->key_pair); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */
|
|
}
|
|
|
|
/* ************************************************************************
|
|
* MARK: PRIVATE
|
|
* *************************************************************************/
|
|
|
|
/**
|
|
* Create the internal key pair in binary
|
|
*
|
|
* @param ?string $key_pair
|
|
* @return string
|
|
* @throws \UnexpectedValueException key pair empty
|
|
* @throws \UnexpectedValueException invalid hex key pair
|
|
* @throws \RangeException key pair not correct size
|
|
*/
|
|
private function createKeyPair(
|
|
#[\SensitiveParameter]
|
|
?string $key_pair
|
|
): string {
|
|
if (empty($key_pair)) {
|
|
throw new \UnexpectedValueException('Key pair cannot be empty');
|
|
}
|
|
try {
|
|
$key_pair = CreateKey::hex2bin($key_pair);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($key_pair);
|
|
throw new \UnexpectedValueException('Invalid hex key pair: ' . $e->getMessage());
|
|
}
|
|
if (mb_strlen($key_pair, '8bit') !== SODIUM_CRYPTO_BOX_KEYPAIRBYTES) {
|
|
sodium_memzero($key_pair);
|
|
throw new \RangeException(
|
|
'Key pair is not the correct size (must be '
|
|
. SODIUM_CRYPTO_BOX_KEYPAIRBYTES . ' bytes long).'
|
|
);
|
|
}
|
|
return $key_pair;
|
|
}
|
|
|
|
/**
|
|
* create the internal public key in binary
|
|
*
|
|
* @param ?string $public_key
|
|
* @return string
|
|
* @throws \UnexpectedValueException public key empty
|
|
* @throws \UnexpectedValueException invalid hex key
|
|
* @throws \RangeException invalid key length
|
|
*/
|
|
private function createPublicKey(?string $public_key): string
|
|
{
|
|
if (empty($public_key)) {
|
|
throw new \UnexpectedValueException('Public key cannot be empty');
|
|
}
|
|
try {
|
|
$public_key = CreateKey::hex2bin($public_key);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($public_key);
|
|
throw new \UnexpectedValueException('Invalid hex public key: ' . $e->getMessage());
|
|
}
|
|
if (mb_strlen($public_key, '8bit') !== SODIUM_CRYPTO_BOX_PUBLICKEYBYTES) {
|
|
sodium_memzero($public_key);
|
|
throw new \RangeException(
|
|
'Public key is not the correct size (must be '
|
|
. SODIUM_CRYPTO_BOX_PUBLICKEYBYTES . ' bytes long).'
|
|
);
|
|
}
|
|
return $public_key;
|
|
}
|
|
|
|
/**
|
|
* encrypt a message asymmetric with a bpulic key
|
|
*
|
|
* @param string $message
|
|
* @param ?string $public_key
|
|
* @return string
|
|
* @throws \UnexpectedValueException create encryption failed
|
|
* @throws \UnexpectedValueException convert to base64 failed
|
|
*/
|
|
private function asymmetricEncryption(
|
|
#[\SensitiveParameter]
|
|
string $message,
|
|
?string $public_key
|
|
): string {
|
|
$public_key = $this->createPublicKey($public_key);
|
|
try {
|
|
$encrypted = sodium_crypto_box_seal($message, $public_key);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($message);
|
|
throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage());
|
|
}
|
|
sodium_memzero($message);
|
|
try {
|
|
$result = sodium_bin2base64($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($encrypted);
|
|
throw new \UnexpectedValueException("bin2base64 failed: " . $e->getMessage());
|
|
}
|
|
sodium_memzero($encrypted);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* decrypt a message that is asymmetric encrypted with a key pair
|
|
*
|
|
* @param string $message
|
|
* @param ?string $key_pair
|
|
* @return string
|
|
* @throws \UnexpectedValueException message string empty
|
|
* @throws \UnexpectedValueException base64 decoding failed
|
|
* @throws \UnexpectedValueException decryption failed
|
|
* @throws \UnexpectedValueException could not decrypt message
|
|
*/
|
|
private function asymmetricDecryption(
|
|
#[\SensitiveParameter]
|
|
string $message,
|
|
#[\SensitiveParameter]
|
|
?string $key_pair
|
|
): string {
|
|
if (empty($message)) {
|
|
throw new \UnexpectedValueException('Encrypted string cannot be empty');
|
|
}
|
|
$key_pair = $this->createKeyPair($key_pair);
|
|
try {
|
|
$result = sodium_base642bin($message, SODIUM_BASE64_VARIANT_ORIGINAL);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($message);
|
|
sodium_memzero($key_pair);
|
|
throw new \UnexpectedValueException("base642bin failed: " . $e->getMessage());
|
|
}
|
|
sodium_memzero($message);
|
|
$plaintext = false;
|
|
try {
|
|
$plaintext = sodium_crypto_box_seal_open($result, $key_pair);
|
|
} catch (SodiumException $e) {
|
|
sodium_memzero($message);
|
|
sodium_memzero($key_pair);
|
|
sodium_memzero($result);
|
|
throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage());
|
|
}
|
|
sodium_memzero($key_pair);
|
|
sodium_memzero($result);
|
|
if (!is_string($plaintext)) {
|
|
throw new \UnexpectedValueException('Invalid key pair');
|
|
}
|
|
return $plaintext;
|
|
}
|
|
|
|
/* ************************************************************************
|
|
* MARK: PUBLIC
|
|
* *************************************************************************/
|
|
|
|
/**
|
|
* sets the private key for encryption
|
|
*
|
|
* @param string $key_pair Key pair in hex
|
|
* @return AsymmetricAnonymousEncryption
|
|
* @throws \UnexpectedValueException key pair empty
|
|
*/
|
|
public function setKeyPair(
|
|
#[\SensitiveParameter]
|
|
string $key_pair
|
|
): AsymmetricAnonymousEncryption {
|
|
if (empty($key_pair)) {
|
|
throw new \UnexpectedValueException('Key pair cannot be empty');
|
|
}
|
|
// check if valid;
|
|
$this->createKeyPair($key_pair);
|
|
// set new key pair
|
|
$this->key_pair = $key_pair;
|
|
sodium_memzero($key_pair);
|
|
// set public key if not set
|
|
if (empty($this->public_key)) {
|
|
$this->public_key = CreateKey::getPublicKey($this->key_pair);
|
|
// check if valid
|
|
$this->createPublicKey($this->public_key);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* check if set key pair matches given one
|
|
*
|
|
* @param string $key_pair
|
|
* @return bool
|
|
*/
|
|
public function compareKeyPair(
|
|
#[\SensitiveParameter]
|
|
string $key_pair
|
|
): bool {
|
|
return $this->key_pair === $key_pair;
|
|
}
|
|
|
|
/**
|
|
* get the current set key pair, null if not set
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getKeyPair(): ?string
|
|
{
|
|
return $this->key_pair;
|
|
}
|
|
|
|
/**
|
|
* sets the public key for decryption
|
|
* if only key pair exists Security\Create::getPublicKey() can be used to
|
|
* extract the public key from the key pair
|
|
*
|
|
* @param string $public_key Public Key in hex
|
|
* @return AsymmetricAnonymousEncryption
|
|
* @throws \UnexpectedValueException public key empty
|
|
*/
|
|
public function setPublicKey(string $public_key): AsymmetricAnonymousEncryption
|
|
{
|
|
if (empty($public_key)) {
|
|
throw new \UnexpectedValueException('Public key cannot be empty');
|
|
}
|
|
// check if valid
|
|
$this->createPublicKey($public_key);
|
|
$this->public_key = $public_key;
|
|
sodium_memzero($public_key);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* check if the set public key matches the given one
|
|
*
|
|
* @param string $public_key
|
|
* @return bool
|
|
*/
|
|
public function comparePublicKey(string $public_key): bool
|
|
{
|
|
return $this->public_key === $public_key;
|
|
}
|
|
|
|
/**
|
|
* get the current set public key, null if not set
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getPublicKey(): ?string
|
|
{
|
|
return $this->public_key;
|
|
}
|
|
|
|
/**
|
|
* Encrypt a message with a public key
|
|
* static version
|
|
*
|
|
* @param string $message Message to encrypt
|
|
* @param string $public_key Public key in hex to encrypt message with
|
|
* @return string Encrypted message as hex string
|
|
*/
|
|
public static function encryptKey(
|
|
#[\SensitiveParameter]
|
|
string $message,
|
|
string $public_key
|
|
): string {
|
|
return self::getInstance()->asymmetricEncryption($message, $public_key);
|
|
}
|
|
|
|
/**
|
|
* Encrypt a message
|
|
*
|
|
* @param string $message Message to ecnrypt
|
|
* @return string Encrypted message as hex string
|
|
*/
|
|
public function encrypt(
|
|
#[\SensitiveParameter]
|
|
string $message
|
|
): string {
|
|
return $this->asymmetricEncryption($message, $this->public_key);
|
|
}
|
|
|
|
/**
|
|
* decrypt a message with a key pair
|
|
* static version
|
|
*
|
|
* @param string $message Message to decrypt in hex
|
|
* @param string $key_pair Key pair in hex to decrypt the message with
|
|
* @return string Decrypted message
|
|
*/
|
|
public static function decryptKey(
|
|
#[\SensitiveParameter]
|
|
string $message,
|
|
#[\SensitiveParameter]
|
|
string $key_pair
|
|
): string {
|
|
return self::getInstance()->asymmetricDecryption($message, $key_pair);
|
|
}
|
|
|
|
/**
|
|
* decrypt a message
|
|
*
|
|
* @param string $message Message to decrypt in hex
|
|
* @return string Decrypted message
|
|
*/
|
|
public function decrypt(
|
|
#[\SensitiveParameter]
|
|
string $message
|
|
): string {
|
|
return $this->asymmetricDecryption($message, $this->key_pair);
|
|
}
|
|
}
|
|
|
|
// __END__
|