From ac037eabde233ce439ea8669967eec05d0c23223 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 24 May 2023 15:47:02 +0900 Subject: [PATCH] New Secruity namespace added Move Passwords from Check to Security and deprecate old Add new SymmetricEncryption and CreateKey CreateKey class just creates keys for the SymmetricEncryption SymmetricEncryption uses the hex2bin calls to convert the hex key to the internal binary key Example: $key = CreateKey::generateRandomKey(); $encrypted = SymmetricEncryption::encrypt($string, $key); $decrypted = SymmetricEncryption::decrypt($encrypted, $key); Above $key must be stored in some secure location (.env file) --- .../CoreLibsSecurityPasswordTest.php} | 10 +- .../CoreLibsSecuritySymmetricEncryption.php | 172 ++++++++++++++++++ phpstan.neon | 6 +- www/admin/class_test.db.single.php | 3 - www/admin/class_test.encryption.php | 111 +++++++++++ www/admin/class_test.password.php | 8 +- www/admin/class_test.php | 1 + www/lib/CoreLibs/ACL/Login.php | 2 +- www/lib/CoreLibs/Basic.php | 6 +- www/lib/CoreLibs/Check/Password.php | 39 ++-- www/lib/CoreLibs/Output/Form/Generate.php | 2 +- www/lib/CoreLibs/Security/CreateKey.php | 61 +++++++ www/lib/CoreLibs/Security/Password.php | 59 ++++++ .../CoreLibs/Security/SymmetricEncryption.php | 96 ++++++++++ www/vendor/composer/autoload_classmap.php | 3 + www/vendor/composer/autoload_static.php | 3 + 16 files changed, 548 insertions(+), 34 deletions(-) rename 4dev/tests/{Check/CoreLibsCheckPasswordTest.php => Security/CoreLibsSecurityPasswordTest.php} (82%) create mode 100644 4dev/tests/Security/CoreLibsSecuritySymmetricEncryption.php create mode 100644 www/admin/class_test.encryption.php create mode 100644 www/lib/CoreLibs/Security/CreateKey.php create mode 100644 www/lib/CoreLibs/Security/Password.php create mode 100644 www/lib/CoreLibs/Security/SymmetricEncryption.php diff --git a/4dev/tests/Check/CoreLibsCheckPasswordTest.php b/4dev/tests/Security/CoreLibsSecurityPasswordTest.php similarity index 82% rename from 4dev/tests/Check/CoreLibsCheckPasswordTest.php rename to 4dev/tests/Security/CoreLibsSecurityPasswordTest.php index ce6a749d..b085992e 100644 --- a/4dev/tests/Check/CoreLibsCheckPasswordTest.php +++ b/4dev/tests/Security/CoreLibsSecurityPasswordTest.php @@ -7,9 +7,9 @@ namespace tests; use PHPUnit\Framework\TestCase; /** - * Test class for Check\Password - * @coversDefaultClass \CoreLibs\Check\Password - * @testdox \CoreLibs\Check\Password method tests + * Test class for Security\Password + * @coversDefaultClass \CoreLibs\Security\Password + * @testdox \CoreLibs\Security\Password method tests */ final class CoreLibsCheckPasswordTest extends TestCase { @@ -46,7 +46,7 @@ final class CoreLibsCheckPasswordTest extends TestCase { $this->assertEquals( $expected, - \CoreLibs\Check\Password::passwordVerify($input, \CoreLibs\Check\Password::passwordSet($input_hash)) + \CoreLibs\Security\Password::passwordVerify($input, \CoreLibs\Security\Password::passwordSet($input_hash)) ); } @@ -65,7 +65,7 @@ final class CoreLibsCheckPasswordTest extends TestCase { $this->assertEquals( $expected, - \CoreLibs\Check\Password::passwordRehashCheck($input) + \CoreLibs\Security\Password::passwordRehashCheck($input) ); } } diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryption.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryption.php new file mode 100644 index 00000000..9e2773da --- /dev/null +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryption.php @@ -0,0 +1,172 @@ + [ + 'input' => 'I am a secret', + 'expected' => 'I am a secret', + ], + ]; + } + + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccess(string $input, string $expected): void + { + $key = CreateKey::generateRandomKey(); + $encrypted = SymmetricEncryption::encrypt($input, $key); + $decrypted = SymmetricEncryption::decrypt($encrypted, $key); + + $this->assertEquals( + $expected, + $decrypted + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerEncryptFailed(): array + { + return [ + 'wrong decryption key' => [ + 'input' => 'I am a secret', + 'excpetion_message' => 'Invalid Key' + ], + ]; + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailed(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $encrypted = SymmetricEncryption::encrypt($input, $key); + $wrong_key = CreateKey::generateRandomKey(); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decrypt($encrypted, $wrong_key); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerWrongKey(): array + { + return [ + 'not hex key' => [ + 'key' => 'not_a_hex_key', + 'exception_message' => 'Invalid hex key' + ], + 'too short hex key' => [ + 'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b', + 'excpetion_message' => 'Key is not the correct size (must be ' + . 'SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long).' + ], + ]; + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKey + * @testdox wrong key $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + 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); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decrypt($encrypted, $key); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerWrongCiphertext(): array + { + return [ + 'too short ciphertext' => [ + 'input' => 'short', + 'exception_message' => 'Invalid ciphertext (too short)' + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertext(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decrypt($input, $key); + } +} + +// __END__ diff --git a/phpstan.neon b/phpstan.neon index 5b7b4982..15ed9728 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -39,9 +39,9 @@ parameters: - www/vendor # ignore errores with ignoreErrors: - - # in the class_test tree we allow deprecated calls - message: "#^Call to deprecated method #" - path: %currentWorkingDirectory%/www/admin/class_test.*.php + # - # in the class_test tree we allow deprecated calls + # message: "#^Call to deprecated method #" + # path: %currentWorkingDirectory%/www/admin/class_test.*.php # - '#Expression in empty\(\) is always falsy.#' # - # message: '#Reflection error: [a-zA-Z0-9\\_]+ not found.#' diff --git a/www/admin/class_test.db.single.php b/www/admin/class_test.db.single.php index c70edb4c..778e2730 100644 --- a/www/admin/class_test.db.single.php +++ b/www/admin/class_test.db.single.php @@ -21,10 +21,7 @@ $ECHO_ALL = true; $LOG_FILE_ID = 'classTest-db-single'; ob_end_flush(); -use CoreLibs\Debug\Support as DgS; -use CoreLibs\DB\IO as DbIo; use CoreLibs\Debug\Support; -use CoreLibs\Convert\SetVarType; $log = new CoreLibs\Debug\Logging([ 'log_folder' => BASE . LOG, diff --git a/www/admin/class_test.encryption.php b/www/admin/class_test.encryption.php new file mode 100644 index 00000000..75185cf4 --- /dev/null +++ b/www/admin/class_test.encryption.php @@ -0,0 +1,111 @@ + BASE . LOG, + 'file_id' => $LOG_FILE_ID, + // add file date + 'print_file_date' => true, + // set debug and print flags + 'debug_all' => $DEBUG_ALL, + 'echo_all' => $ECHO_ALL ?? false, + 'print_all' => $PRINT_ALL, +]); + + +// define a list of from to color sets for conversion test + +$PAGE_NAME = 'TEST CLASS: ENCRYPTION'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print '
Class Test Master
'; +print '

' . $PAGE_NAME . '

'; + +$key = CreateKey::generateRandomKey(); +print "Secret Key: " . $key . "
"; + +$string = "I a some deep secret"; +$encrypted = SymmetricEncryption::encrypt($string, $key); +$decrypted = SymmetricEncryption::decrypt($encrypted, $key); + +print "Original: " . $string . "
"; +print "Encrypted: " . $encrypted . "
"; +print "Decrytped: " . $decrypted . "
"; + +print "
WRONG CIPHERTEXT
"; +try { + $decrypted = SymmetricEncryption::decrypt('flupper', $key); +} catch (Exception $e) { + print "Error: " . $e->getMessage() . "
"; +} + +print "
SHORT and WRONG KEY
"; +$key = 'wrong_key'; +try { + $encrypted = SymmetricEncryption::encrypt($string, $key); +} catch (Exception $e) { + print "Error: " . $e->getMessage() . "
"; +} + +print "
INVALID HEX KEY
"; +$key = '1cabd5cba9e042f12522f4ff2de5c31d233b'; +try { + $encrypted = SymmetricEncryption::encrypt($string, $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); +$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); +$key = 'wrong_key'; +try { + $decrypted = SymmetricEncryption::decrypt($encrypted, $key); +} catch (Exception $e) { + print "Error: " . $e->getMessage() . "
"; +} + +// error message +print $log->printErrorMsg(); + +print ""; + +// __END__ diff --git a/www/admin/class_test.password.php b/www/admin/class_test.password.php index 73312bb2..3ca8e668 100644 --- a/www/admin/class_test.password.php +++ b/www/admin/class_test.password.php @@ -20,10 +20,10 @@ define('USE_DATABASE', false); // sample config require 'config.php'; // define log file id -$LOG_FILE_ID = 'classTest-pass'; +$LOG_FILE_ID = 'classTest-password'; ob_end_flush(); -use CoreLibs\Check\Password as PwdChk; +use CoreLibs\Security\Password as PwdChk; $log = new CoreLibs\Debug\Logging([ 'log_folder' => BASE . LOG, @@ -35,8 +35,8 @@ $log = new CoreLibs\Debug\Logging([ 'echo_all' => $ECHO_ALL ?? false, 'print_all' => $PRINT_ALL, ]); -$_password = new CoreLibs\Check\Password(); -$password_class = 'CoreLibs\Check\Password'; +$_password = new CoreLibs\Security\Password(); +$password_class = 'CoreLibs\Security\Password'; // define a list of from to color sets for conversion test diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 9c2b453c..e4c020ca 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -81,6 +81,7 @@ print '
Class Test: MIME
'; print '
Class Test: JSON
'; print '
Class Test: FORM TOKEN
'; print '
Class Test: PASSWORD
'; +print '
Class Test: ENCRYPTION
'; print '
Class Test: MATH
'; print '
Class Test: HTML/ELEMENTS
'; print '
Class Test: EMAIL
'; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 92a87028..e657ee19 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -68,7 +68,7 @@ declare(strict_types=1); namespace CoreLibs\ACL; -use CoreLibs\Check\Password; +use CoreLibs\Security\Password; use CoreLibs\Convert\Json; class Login diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index a9a70c52..7e1882e8 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -1164,7 +1164,7 @@ class Basic public function passwordSet(string $password): string { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Check\Password::passwordSet()', E_USER_DEPRECATED); - return \CoreLibs\Check\Password::passwordSet($password); + return \CoreLibs\Security\Password::passwordSet($password); } /** @@ -1177,7 +1177,7 @@ class Basic public function passwordVerify(string $password, string $hash): bool { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Check\Password::passwordVerify()', E_USER_DEPRECATED); - return \CoreLibs\Check\Password::passwordVerify($password, $hash); + return \CoreLibs\Security\Password::passwordVerify($password, $hash); } /** @@ -1189,7 +1189,7 @@ class Basic public function passwordRehashCheck(string $hash): bool { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Check\Password::passwordRehashCheck()', E_USER_DEPRECATED); - return \CoreLibs\Check\Password::passwordRehashCheck($hash); + return \CoreLibs\Security\Password::passwordRehashCheck($hash); } // *** BETTER PASSWORD OPTIONS END *** diff --git a/www/lib/CoreLibs/Check/Password.php b/www/lib/CoreLibs/Check/Password.php index 1475064c..f1f588b1 100644 --- a/www/lib/CoreLibs/Check/Password.php +++ b/www/lib/CoreLibs/Check/Password.php @@ -1,6 +1,8 @@ table_array[$key]['value']) { // use the better new passwordSet instead of crypt based $this->table_array[$key]['value'] = - \CoreLibs\Check\Password::passwordSet($this->table_array[$key]['value']); + \CoreLibs\Security\Password::passwordSet($this->table_array[$key]['value']); $this->table_array[$key]['HIDDEN_value'] = $this->table_array[$key]['value']; } else { // $this->table_array[$key]['HIDDEN_value'] = diff --git a/www/lib/CoreLibs/Security/CreateKey.php b/www/lib/CoreLibs/Security/CreateKey.php new file mode 100644 index 00000000..add2773e --- /dev/null +++ b/www/lib/CoreLibs/Security/CreateKey.php @@ -0,0 +1,61 @@ + $baseDir . '/lib/CoreLibs/Output/Form/Token.php', 'CoreLibs\\Output\\Image' => $baseDir . '/lib/CoreLibs/Output/Image.php', 'CoreLibs\\Output\\ProgressBar' => $baseDir . '/lib/CoreLibs/Output/ProgressBar.php', + 'CoreLibs\\Security\\CreateKey' => $baseDir . '/lib/CoreLibs/Security/CreateKey.php', + 'CoreLibs\\Security\\Password' => $baseDir . '/lib/CoreLibs/Security/Password.php', + 'CoreLibs\\Security\\SymmetricEncryption' => $baseDir . '/lib/CoreLibs/Security/SymmetricEncryption.php', 'CoreLibs\\Template\\SmartyExtend' => $baseDir . '/lib/CoreLibs/Template/SmartyExtend.php', 'FileUpload\\Core\\qqUploadedFile' => $baseDir . '/lib/FileUpload/Core/qqUploadedFile.php', 'FileUpload\\Core\\qqUploadedFileForm' => $baseDir . '/lib/FileUpload/Core/qqUploadedFileForm.php', diff --git a/www/vendor/composer/autoload_static.php b/www/vendor/composer/autoload_static.php index cc3bb35b..7ff5acf8 100644 --- a/www/vendor/composer/autoload_static.php +++ b/www/vendor/composer/autoload_static.php @@ -115,6 +115,9 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9 'CoreLibs\\Output\\Form\\Token' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Token.php', 'CoreLibs\\Output\\Image' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Image.php', 'CoreLibs\\Output\\ProgressBar' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/ProgressBar.php', + 'CoreLibs\\Security\\CreateKey' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/CreateKey.php', + 'CoreLibs\\Security\\Password' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/Password.php', + 'CoreLibs\\Security\\SymmetricEncryption' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/SymmetricEncryption.php', 'CoreLibs\\Template\\SmartyExtend' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/SmartyExtend.php', 'FileUpload\\Core\\qqUploadedFile' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFile.php', 'FileUpload\\Core\\qqUploadedFileForm' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFileForm.php',