diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index 33a2f458..d3f502af 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -46,12 +46,34 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase public function testEncryptDecryptSuccess(string $input, string $expected): void { $key = CreateKey::generateRandomKey(); - $encrypted = SymmetricEncryption::encrypt($input, $key); - $decrypted = SymmetricEncryption::decrypt($encrypted, $key); + + // test class + $crypt = new SymmetricEncryption($key); + $encrypted = $crypt->encrypt($input); + $decrypted = $crypt->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class call', + ); + + // test indirect + $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); + $decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class Instance call', + ); + + // test static + $encrypted = SymmetricEncryption::encryptKey($input, $key); + $decrypted = SymmetricEncryption::decryptKey($encrypted, $key); $this->assertEquals( $expected, - $decrypted + $decrypted, + 'Static call', ); } @@ -86,10 +108,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase public function testEncryptFailed(string $input, string $exception_message): void { $key = CreateKey::generateRandomKey(); - $encrypted = SymmetricEncryption::encrypt($input, $key); $wrong_key = CreateKey::generateRandomKey(); + + // wrong key in class call + $crypt = new SymmetricEncryption($key); + $encrypted = $crypt->encrypt($input); $this->expectExceptionMessage($exception_message); - SymmetricEncryption::decrypt($encrypted, $wrong_key); + $crypt->setKey($key); + $crypt->decrypt($encrypted); + + // class instance + $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted); + + // class static + $encrypted = SymmetricEncryption::encryptKey($input, $key); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($encrypted, $wrong_key); } /** @@ -107,7 +143,6 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase 'too short hex key' => [ 'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b', 'excpetion_message' => 'Key is not the correct size (must be ' - . 'SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long).' ], ]; } @@ -126,13 +161,33 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase */ public function testWrongKey(string $key, string $exception_message): void { - $this->expectExceptionMessage($exception_message); - SymmetricEncryption::encrypt('test', $key); - // we must encrypt valid thing first so we can fail with the wrong kjey $enc_key = CreateKey::generateRandomKey(); - $encrypted = SymmetricEncryption::encrypt('test', $enc_key); + + // class + $crypt = new SymmetricEncryption($key); $this->expectExceptionMessage($exception_message); - SymmetricEncryption::decrypt($encrypted, $key); + $crypt->encrypt('test'); + $crypt->setKey($enc_key); + $encrypted = $crypt->encrypt('test'); + $this->expectExceptionMessage($exception_message); + $crypt->setKey($key); + $crypt->decrypt($encrypted); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->encrypt('test'); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test'); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($encrypted); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::encryptKey('test', $key); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::encryptKey('test', $enc_key); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($encrypted, $key); } /** @@ -145,7 +200,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase return [ 'too short ciphertext' => [ 'input' => 'short', - 'exception_message' => 'Invalid ciphertext (too short)' + 'exception_message' => 'Decipher message failed: ' ], ]; } @@ -164,8 +219,18 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase public function testWrongCiphertext(string $input, string $exception_message): void { $key = CreateKey::generateRandomKey(); + // class + $crypt = new SymmetricEncryption($key); $this->expectExceptionMessage($exception_message); - SymmetricEncryption::decrypt($input, $key); + $crypt->decrypt($input); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($input); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); } } diff --git a/www/admin/class_test.encryption.php b/www/admin/class_test.encryption.php index c03e5515..77e16e23 100644 --- a/www/admin/class_test.encryption.php +++ b/www/admin/class_test.encryption.php @@ -40,16 +40,33 @@ $key = CreateKey::generateRandomKey(); print "Secret Key: " . $key . "
"; $string = "I a some deep secret"; -$encrypted = SymmetricEncryption::encrypt($string, $key); -$decrypted = SymmetricEncryption::decrypt($encrypted, $key); +// +$crypt = new SymmetricEncryption($key); +$encrypted = $crypt->encrypt($string); +$decrypted = $crypt->decrypt($encrypted); +print "[C] Encrypted: " . $encrypted . "
"; +print "[C] Decrytped: " . $decrypted . "
"; +$encrypted = SymmetricEncryption::getInstance($key)->encrypt($string); +$decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted); +print "[S] Original: " . $string . "
"; +print "[S] Encrypted: " . $encrypted . "
"; +print "[S] Decrytped: " . $decrypted . "
"; +$encrypted = SymmetricEncryption::encryptKey($string, $key); +$decrypted = SymmetricEncryption::decryptKey($encrypted, $key); +print "[SS] Encrypted: " . $encrypted . "
"; +print "[SS] Decrytped: " . $decrypted . "
"; -print "Original: " . $string . "
"; -print "Encrypted: " . $encrypted . "
"; -print "Decrytped: " . $decrypted . "
"; +print "
INIT KEY MISSING
"; +try { + $crypt = new SymmetricEncryption(); + $encrypted = $crypt->decrypt($string); +} catch (Exception $e) { + print("Error: " . $e->getMessage() . "
"); +} print "
WRONG CIPHERTEXT
"; try { - $decrypted = SymmetricEncryption::decrypt('flupper', $key); + $decrypted = SymmetricEncryption::decryptKey('flupper', $key); } catch (Exception $e) { print "Error: " . $e->getMessage() . "
"; } @@ -57,7 +74,7 @@ try { print "
SHORT and WRONG KEY
"; $key = 'wrong_key'; try { - $encrypted = SymmetricEncryption::encrypt($string, $key); + $encrypted = SymmetricEncryption::encryptKey($string, $key); } catch (Exception $e) { print "Error: " . $e->getMessage() . "
"; } @@ -65,7 +82,7 @@ try { print "
INVALID HEX KEY
"; $key = '1cabd5cba9e042f12522f4ff2de5c31d233b'; try { - $encrypted = SymmetricEncryption::encrypt($string, $key); + $encrypted = SymmetricEncryption::encryptKey($string, $key); } catch (Exception $e) { print "Error: " . $e->getMessage() . "
"; } @@ -73,21 +90,10 @@ try { print "
WRONG KEY TO DECRYPT
"; $key = CreateKey::generateRandomKey(); $string = "I a some deep secret"; -$encrypted = SymmetricEncryption::encrypt($string, $key); -$key = CreateKey::generateRandomKey(); -try { - $decrypted = SymmetricEncryption::decrypt($encrypted, $key); -} catch (Exception $e) { - print "Error: " . $e->getMessage() . "
"; -} - -print "
WRONG KEY TO DECRYPT
"; -$key = CreateKey::generateRandomKey(); -$string = "I a some deep secret"; -$encrypted = SymmetricEncryption::encrypt($string, $key); +$encrypted = SymmetricEncryption::encryptKey($string, $key); $key = 'wrong_key'; try { - $decrypted = SymmetricEncryption::decrypt($encrypted, $key); + $decrypted = SymmetricEncryption::decryptKey($encrypted, $key); } catch (Exception $e) { print "Error: " . $e->getMessage() . "
"; } diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index 2586e94a..c12f4b3f 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -21,58 +21,82 @@ use SodiumException; class SymmetricEncryption { + /** @var SymmetricEncryption self instance */ + private static SymmetricEncryption $instance; + + /** @var string bin hex key */ + private string $key = ''; + /** - * Encrypt a message + * init class + * if key not passed, key must be set with createKey * - * @param string $message Message to encrypt - * @param string $key Encryption key (as hex string) - * @return string - * @throws \Exception - * @throws \RangeException + * @param string|null|null $key */ - public static function encrypt(string $message, string $key): string + public function __construct( + string|null $key = null + ) { + if ($key != null) { + $this->setKey($key); + } + } + + /** + * Returns the singleton self object. + * For function wrapper use + * + * @return SymmetricEncryption object + */ + public static function getInstance(string|null $key = null): self + { + if (empty(self::$instance)) { + self::$instance = new self($key); + } + return self::$instance; + } + + /* ************************************************************************ + * MARK: PRIVATE + * *************************************************************************/ + + /** + * create key and check validity + * + * @param string $key The key from which the binary key will be created + * @return string Binary key string + */ + private function createKey(string $key): string { try { $key = CreateKey::hex2bin($key); } catch (SodiumException $e) { - throw new \UnexpectedValueException('Invalid hex key'); + throw new \UnexpectedValueException('Invalid hex key: ' . $e->getMessage()); } if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) { throw new \RangeException( 'Key is not the correct size (must be ' - . 'SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long).' + . SODIUM_CRYPTO_SECRETBOX_KEYBYTES . ' bytes long).' ); } - $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); - - $cipher = base64_encode( - $nonce - . sodium_crypto_secretbox( - $message, - $nonce, - $key - ) - ); - sodium_memzero($message); - sodium_memzero($key); - return $cipher; + return $key; } /** - * Decrypt a message + * Decryption call * - * @param string $encrypted Message encrypted with safeEncrypt() - * @param string $key Encryption key (as hex string) - * @return string - * @throws \Exception + * @param string $encrypted Text to decrypt + * @param ?string $key Mandatory encryption key, will throw exception if empty + * @return string Plain text + * @throws \RangeException + * @throws \UnexpectedValueException + * @throws \UnexpectedValueException */ - public static function decrypt(string $encrypted, string $key): string + private function decryptData(string $encrypted, ?string $key): string { - try { - $key = CreateKey::hex2bin($key); - } catch (SodiumException $e) { - throw new \Exception('Invalid hex key'); + if (empty($key)) { + throw new \UnexpectedValueException('Key not set'); } + $key = $this->createKey($key); $decoded = base64_decode($encrypted); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); @@ -85,7 +109,7 @@ class SymmetricEncryption $key ); } catch (SodiumException $e) { - throw new \UnexpectedValueException('Invalid ciphertext (too short)'); + throw new \UnexpectedValueException('Decipher message failed: ' . $e->getMessage()); } if (!is_string($plain)) { throw new \UnexpectedValueException('Invalid Key'); @@ -94,6 +118,117 @@ class SymmetricEncryption sodium_memzero($key); return $plain; } + + /** + * Encrypt a message + * + * @param string $message Message to encrypt + * @param ?string $key Mandatory encryption key, will throw exception if empty + * @return string + * @throws \Exception + * @throws \RangeException + */ + private function encryptData(string $message, ?string $key): string + { + if (empty($this->key) || $key === null) { + throw new \UnexpectedValueException('Key not set'); + } + $key = $this->createKey($key); + $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); + try { + $cipher = base64_encode( + $nonce + . sodium_crypto_secretbox( + $message, + $nonce, + $key, + ) + ); + } catch (SodiumException $e) { + throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage()); + } + sodium_memzero($message); + sodium_memzero($key); + return $cipher; + } + + /* ************************************************************************ + * MARK: PUBLIC + * *************************************************************************/ + + + /** + * set a new key for encryption + * + * @param string $key + * @return void + */ + public function setKey(string $key) + { + if (empty($key)) { + throw new \UnexpectedValueException('Key cannot be empty'); + } + $this->key = $key; + } + + /** + * Decrypt a message + * static version + * + * @param string $encrypted Message encrypted with safeEncrypt() + * @param string $key Encryption key (as hex string) + * @return string + * @throws \Exception + * @throws \RangeException + * @throws \UnexpectedValueException + * @throws \UnexpectedValueException + */ + public static function decryptKey(string $encrypted, string $key): string + { + return self::getInstance()->decryptData($encrypted, $key); + } + + /** + * Decrypt a message + * + * @param string $encrypted Message encrypted with safeEncrypt() + * @return string + * @throws \RangeException + * @throws \UnexpectedValueException + * @throws \UnexpectedValueException + */ + public function decrypt(string $encrypted): string + { + return $this->decryptData($encrypted, $this->key); + } + + /** + * Encrypt a message + * static version + * + * @param string $message Message to encrypt + * @param string $key Encryption key (as hex string) + * @return string + * @throws \Exception + * @throws \RangeException + */ + public static function encryptKey(string $message, string $key): string + { + return self::getInstance()->encryptData($message, $key); + } + + /** + * Encrypt a message + * + * @param string $message Message to encrypt + * @return string + * @throws \Exception + * @throws \RangeException + */ + public function encrypt(string $message): string + { + return $this->encryptData($message, $this->key); + } } // __END__