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)
This commit is contained in:
Clemens Schwaighofer
2023-05-24 15:47:02 +09:00
parent 0250b86b3f
commit ac037eabde
16 changed files with 548 additions and 34 deletions

View File

@@ -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)
);
}
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Security\CreateKey;
use CoreLibs\Security\SymmetricEncryption;
/**
* Test class for Security\SymmetricEncryption and Security\CreateKey
* @coversDefaultClass \CoreLibs\Security\SymmetricEncryption
* @testdox \CoreLibs\Security\SymmetricEncryption method tests
*/
final class CoreLibsSecuritySymmetricEncryption extends TestCase
{
/**
* Undocumented function
*
* @return array
*/
public function providerEncryptDecryptSuccess(): array
{
return [
'valid string' => [
'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__

View File

@@ -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.#'

View File

@@ -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,

View File

@@ -0,0 +1,111 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
$DEBUG_ALL_OVERRIDE = false; // set to 1 to debug on live/remote server locations
$DEBUG_ALL = true;
$PRINT_ALL = true;
$DB_DEBUG = true;
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', false);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-encryption';
ob_end_flush();
use CoreLibs\Security\SymmetricEncryption;
use CoreLibs\Security\CreateKey;
$log = new CoreLibs\Debug\Logging([
'log_folder' => 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 "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>";
$string = "I a some deep secret";
$encrypted = SymmetricEncryption::encrypt($string, $key);
$decrypted = SymmetricEncryption::decrypt($encrypted, $key);
print "Original: " . $string . "<br>";
print "Encrypted: " . $encrypted . "<br>";
print "Decrytped: " . $decrypted . "<br>";
print "<br>WRONG CIPHERTEXT<br>";
try {
$decrypted = SymmetricEncryption::decrypt('flupper', $key);
} catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>";
}
print "<br>SHORT and WRONG KEY<br>";
$key = 'wrong_key';
try {
$encrypted = SymmetricEncryption::encrypt($string, $key);
} catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>";
}
print "<br>INVALID HEX KEY<br>";
$key = '1cabd5cba9e042f12522f4ff2de5c31d233b';
try {
$encrypted = SymmetricEncryption::encrypt($string, $key);
} catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>";
}
print "<br>WRONG KEY TO DECRYPT<br>";
$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() . "<br>";
}
print "<br>WRONG KEY TO DECRYPT<br>";
$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() . "<br>";
}
// error message
print $log->printErrorMsg();
print "</body></html>";
// __END__

View File

@@ -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

View File

@@ -81,6 +81,7 @@ print '<div><a href="class_test.mime.php">Class Test: MIME</a></div>';
print '<div><a href="class_test.json.php">Class Test: JSON</a></div>';
print '<div><a href="class_test.token.php">Class Test: FORM TOKEN</a></div>';
print '<div><a href="class_test.password.php">Class Test: PASSWORD</a></div>';
print '<div><a href="class_test.encryption.php">Class Test: ENCRYPTION</a></div>';
print '<div><a href="class_test.math.php">Class Test: MATH</a></div>';
print '<div><a href="class_test.html.php">Class Test: HTML/ELEMENTS</a></div>';
print '<div><a href="class_test.email.php">Class Test: EMAIL</a></div>';

View File

@@ -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

View File

@@ -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 ***

View File

@@ -1,6 +1,8 @@
<?php
/*
* NOTE: this is deprecated and all moved \CoreLibs\Security\Password
*
* core password set, check and rehash check wrapper functions
*/
@@ -8,6 +10,8 @@ declare(strict_types=1);
namespace CoreLibs\Check;
use CoreLibs\Security\Password as PasswordNew;
class Password
{
/**
@@ -15,13 +19,16 @@ class Password
*
* @param string $password password
* @return string hashed password
* @deprecated v9.0 Moved to \CoreLibs\Security\Password::passwordSet
*/
public static function passwordSet(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
return password_hash($password, PASSWORD_DEFAULT);
trigger_error(
'Method ' . __METHOD__ . ' is deprecated, use '
. '\CoreLibs\Security\Password::passwordSet',
E_USER_DEPRECATED
);
return PasswordNew::passwordSet($password);
}
/**
@@ -30,14 +37,16 @@ class Password
* @param string $password password
* @param string $hash password hash
* @return bool true or false
* @deprecated v9.0 Moved to \CoreLibs\Security\Password::passwordVerify
*/
public static function passwordVerify(string $password, string $hash): bool
{
if (password_verify($password, $hash)) {
return true;
} else {
return false;
}
trigger_error(
'Method ' . __METHOD__ . ' is deprecated, use '
. '\CoreLibs\Security\Password::passwordVerify',
E_USER_DEPRECATED
);
return PasswordNew::passwordVerify($password, $hash);
}
/**
@@ -45,14 +54,16 @@ class Password
*
* @param string $hash password hash
* @return bool true or false
* @deprecated v9.0 Moved to \CoreLibs\Security\Password::passwordRehashCheck
*/
public static function passwordRehashCheck(string $hash): bool
{
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
return true;
} else {
return false;
}
trigger_error(
'Method ' . __METHOD__ . ' is deprecated, use '
. '\CoreLibs\Security\Password::passwordRehashCheck',
E_USER_DEPRECATED
);
return PasswordNew::passwordRehashCheck($hash);
}
}

View File

@@ -1954,7 +1954,7 @@ class Generate extends \CoreLibs\DB\Extended\ArrayIO
if ($this->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'] =

View File

@@ -0,0 +1,61 @@
<?php
/**
* very simple symmetric encryption
* better use: https://paragonie.com/project/halite
*
* this is for creating secret keys for
* Security\SymmetricEncryption
*/
declare(strict_types=1);
namespace CoreLibs\Security;
class CreateKey
{
/**
* Create a random key that is a hex string
*
* @return string Hex string key for encrypting
*/
public static function generateRandomKey(): string
{
return self::bin2hex(self::randomKey());
}
/**
* create a random string as binary to encrypt data
* to store it in clear text in some .env file use bin2hex
*
* @return string Binary string for encryption
*/
public static function randomKey(): string
{
return random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
}
/**
* 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
{
return sodium_bin2hex($hex_key);
}
/**
* convert hex string to binary key
*
* @param string $string_key Convery hex key string to binary
* @return string
*/
public static function hex2bin(string $string_key): string
{
return sodium_hex2bin($string_key);
}
}
// __END__

View File

@@ -0,0 +1,59 @@
<?php
/*
* core password set, check and rehash check wrapper functions
*/
declare(strict_types=1);
namespace CoreLibs\Security;
class Password
{
/**
* creates the password hash
*
* @param string $password password
* @return string hashed password
*/
public static function passwordSet(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
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* checks if the entered password matches the hash
*
* @param string $password password
* @param string $hash password hash
* @return bool true or false
*/
public static function passwordVerify(string $password, string $hash): bool
{
if (password_verify($password, $hash)) {
return true;
} else {
return false;
}
}
/**
* checks if the password needs to be rehashed
*
* @param string $hash password hash
* @return bool true or false
*/
public static function passwordRehashCheck(string $hash): bool
{
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
return true;
} else {
return false;
}
}
}
// __END__

View File

@@ -0,0 +1,96 @@
<?php
/**
* very simple symmetric encryption
* Better use: https://paragonie.com/project/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 SymmetricEncryption
{
/**
* Encrypt a message
*
* @param string $message Message to encrypt
* @param string $key Encryption key (as hex string)
* @return string
* @throws \RangeException
*/
public static function encrypt(string $message, string $key): string
{
try {
$key = CreateKey::hex2bin($key);
} catch (SodiumException $e) {
throw new \Exception('Invalid hex key');
}
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).'
);
}
$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;
}
/**
* Decrypt a message
*
* @param string $encrypted Message encrypted with safeEncrypt()
* @param string $key Encryption key (as hex string)
* @return string
* @throws \Exception
*/
public static function decrypt(string $encrypted, string $key): string
{
try {
$key = CreateKey::hex2bin($key);
} catch (SodiumException $e) {
throw new \Exception('Invalid hex 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');
$plain = false;
try {
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
} catch (SodiumException $e) {
throw new \Exception('Invalid ciphertext (too short)');
}
if (!is_string($plain)) {
throw new \Exception('Invalid Key');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
}
// __END__

View File

@@ -69,6 +69,9 @@ return array(
'CoreLibs\\Output\\Form\\Token' => $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',

View File

@@ -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',