diff --git a/www/lib/CoreLibs/Security/AsymmetricEncryption.php b/www/lib/CoreLibs/Security/AsymmetricEncryption.php new file mode 100644 index 00000000..6d3f3749 --- /dev/null +++ b/www/lib/CoreLibs/Security/AsymmetricEncryption.php @@ -0,0 +1,380 @@ +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 = 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); + } + + /* ************************************************************************ + * MARK: PRIVATE + * *************************************************************************/ + + /** + * Create the internal key pair in binary + * + * @param ?string $key_pair + * @return string + */ + 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 + */ + 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 + */ + 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 + */ + private function asymmetricDecryption( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + ?string $key_pair + ): string { + if (empty($message)) { + throw new \UnexpectedValueException('Message 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); + throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage()); + } + if (!is_string($plaintext)) { + sodium_memzero($key_pair); + throw new \UnexpectedValueException('Could not decrypt message'); + } + sodium_memzero($result); + sodium_memzero($key_pair); + return $plaintext; + } + + /* ************************************************************************ + * MARK: PUBLIC + * *************************************************************************/ + + /** + * sets the private key for encryption + * + * @param string $key_pair Key pair in hex + * @return void + */ + public function setKeyPair( + #[\SensitiveParameter] + string $key_pair + ) { + if (empty($key_pair)) { + throw new \UnexpectedValueException('Key pair cannot be empty'); + } + $this->key_pair = $key_pair; + sodium_memzero($key_pair); + } + + /** + * 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 void + */ + public function setPublicKey(string $public_key) + { + if (empty($public_key)) { + throw new \UnexpectedValueException('Public key cannot be empty'); + } + $this->public_key = $public_key; + sodium_memzero($public_key); + } + + /** + * 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__ diff --git a/www/lib/CoreLibs/Security/CreateKey.php b/www/lib/CoreLibs/Security/CreateKey.php index add2773e..e9f7c53c 100644 --- a/www/lib/CoreLibs/Security/CreateKey.php +++ b/www/lib/CoreLibs/Security/CreateKey.php @@ -35,14 +35,39 @@ class CreateKey return random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); } + /** + * creates a sodium cyptobox keypair as hex string + * + * @return string hex string for the keypair + */ + public static function createKeyPair(): string + { + return self::bin2hex(sodium_crypto_box_keypair()); + } + + /** + * extracts the public key and returns it as hex string from the hex keypari + * + * @param string $hex_keypair hex encoded keypair + * @return string hex encoded public key + */ + public static function getPublicKey( + #[\SensitiveParameter] + string $hex_keypair + ): string { + return self::bin2hex(sodium_crypto_box_publickey(self::hex2bin($hex_keypair))); + } + /** * convert binary key to hex string * * @param string $hex_key Convert binary key string to hex * @return string */ - public static function bin2hex(string $hex_key): string - { + public static function bin2hex( + #[\SensitiveParameter] + string $hex_key + ): string { return sodium_bin2hex($hex_key); } @@ -52,8 +77,10 @@ class CreateKey * @param string $string_key Convery hex key string to binary * @return string */ - public static function hex2bin(string $string_key): string - { + public static function hex2bin( + #[\SensitiveParameter] + string $string_key + ): string { return sodium_hex2bin($string_key); } } diff --git a/www/lib/CoreLibs/Security/Password.php b/www/lib/CoreLibs/Security/Password.php index 984fa5cb..8c64228e 100644 --- a/www/lib/CoreLibs/Security/Password.php +++ b/www/lib/CoreLibs/Security/Password.php @@ -16,8 +16,10 @@ class Password * @param string $password password * @return string hashed password */ - public static function passwordSet(string $password): string - { + public static function passwordSet( + #[\SensitiveParameter] + string $password + ): string { // always use the PHP default for the password // password options ca be set in the password init, // but should be kept as default @@ -31,8 +33,11 @@ class Password * @param string $hash password hash * @return bool true or false */ - public static function passwordVerify(string $password, string $hash): bool - { + public static function passwordVerify( + #[\SensitiveParameter] + string $password, + string $hash + ): bool { if (password_verify($password, $hash)) { return true; } else {