Update Symmetric Encryption

Can be used as a class with central key set.

for old static calls:
encrypt -> encryptKey
decrypt -> decryptKey
This commit is contained in:
Clemens Schwaighofer
2024-05-22 10:43:54 +09:00
parent e933022671
commit 0524d8ac1b
3 changed files with 273 additions and 67 deletions

View File

@@ -46,12 +46,34 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
public function testEncryptDecryptSuccess(string $input, string $expected): void public function testEncryptDecryptSuccess(string $input, string $expected): void
{ {
$key = CreateKey::generateRandomKey(); $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( $this->assertEquals(
$expected, $expected,
$decrypted $decrypted,
'Static call',
); );
} }
@@ -86,10 +108,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
public function testEncryptFailed(string $input, string $exception_message): void public function testEncryptFailed(string $input, string $exception_message): void
{ {
$key = CreateKey::generateRandomKey(); $key = CreateKey::generateRandomKey();
$encrypted = SymmetricEncryption::encrypt($input, $key);
$wrong_key = CreateKey::generateRandomKey(); $wrong_key = CreateKey::generateRandomKey();
// wrong key in class call
$crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($input);
$this->expectExceptionMessage($exception_message); $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' => [ 'too short hex key' => [
'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b', 'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b',
'excpetion_message' => 'Key is not the correct size (must be ' '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 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(); $enc_key = CreateKey::generateRandomKey();
$encrypted = SymmetricEncryption::encrypt('test', $enc_key);
// class
$crypt = new SymmetricEncryption($key);
$this->expectExceptionMessage($exception_message); $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 [ return [
'too short ciphertext' => [ 'too short ciphertext' => [
'input' => 'short', '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 public function testWrongCiphertext(string $input, string $exception_message): void
{ {
$key = CreateKey::generateRandomKey(); $key = CreateKey::generateRandomKey();
// class
$crypt = new SymmetricEncryption($key);
$this->expectExceptionMessage($exception_message); $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);
} }
} }

View File

@@ -40,16 +40,33 @@ $key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>"; print "Secret Key: " . $key . "<br>";
$string = "I a some deep secret"; $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 . "<br>";
print "[C] Decrytped: " . $decrypted . "<br>";
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($string);
$decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted);
print "[S] Original: " . $string . "<br>";
print "[S] Encrypted: " . $encrypted . "<br>";
print "[S] Decrytped: " . $decrypted . "<br>";
$encrypted = SymmetricEncryption::encryptKey($string, $key);
$decrypted = SymmetricEncryption::decryptKey($encrypted, $key);
print "[SS] Encrypted: " . $encrypted . "<br>";
print "[SS] Decrytped: " . $decrypted . "<br>";
print "Original: " . $string . "<br>"; print "<br>INIT KEY MISSING<br>";
print "Encrypted: " . $encrypted . "<br>"; try {
print "Decrytped: " . $decrypted . "<br>"; $crypt = new SymmetricEncryption();
$encrypted = $crypt->decrypt($string);
} catch (Exception $e) {
print("Error: " . $e->getMessage() . "<br>");
}
print "<br>WRONG CIPHERTEXT<br>"; print "<br>WRONG CIPHERTEXT<br>";
try { try {
$decrypted = SymmetricEncryption::decrypt('flupper', $key); $decrypted = SymmetricEncryption::decryptKey('flupper', $key);
} catch (Exception $e) { } catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>"; print "Error: " . $e->getMessage() . "<br>";
} }
@@ -57,7 +74,7 @@ try {
print "<br>SHORT and WRONG KEY<br>"; print "<br>SHORT and WRONG KEY<br>";
$key = 'wrong_key'; $key = 'wrong_key';
try { try {
$encrypted = SymmetricEncryption::encrypt($string, $key); $encrypted = SymmetricEncryption::encryptKey($string, $key);
} catch (Exception $e) { } catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>"; print "Error: " . $e->getMessage() . "<br>";
} }
@@ -65,7 +82,7 @@ try {
print "<br>INVALID HEX KEY<br>"; print "<br>INVALID HEX KEY<br>";
$key = '1cabd5cba9e042f12522f4ff2de5c31d233b'; $key = '1cabd5cba9e042f12522f4ff2de5c31d233b';
try { try {
$encrypted = SymmetricEncryption::encrypt($string, $key); $encrypted = SymmetricEncryption::encryptKey($string, $key);
} catch (Exception $e) { } catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>"; print "Error: " . $e->getMessage() . "<br>";
} }
@@ -73,21 +90,10 @@ try {
print "<br>WRONG KEY TO DECRYPT<br>"; print "<br>WRONG KEY TO DECRYPT<br>";
$key = CreateKey::generateRandomKey(); $key = CreateKey::generateRandomKey();
$string = "I a some deep secret"; $string = "I a some deep secret";
$encrypted = SymmetricEncryption::encrypt($string, $key); $encrypted = SymmetricEncryption::encryptKey($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'; $key = 'wrong_key';
try { try {
$decrypted = SymmetricEncryption::decrypt($encrypted, $key); $decrypted = SymmetricEncryption::decryptKey($encrypted, $key);
} catch (Exception $e) { } catch (Exception $e) {
print "Error: " . $e->getMessage() . "<br>"; print "Error: " . $e->getMessage() . "<br>";
} }

View File

@@ -21,58 +21,82 @@ use SodiumException;
class SymmetricEncryption 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|null|null $key
* @param string $key Encryption key (as hex string)
* @return string
* @throws \Exception
* @throws \RangeException
*/ */
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 { try {
$key = CreateKey::hex2bin($key); $key = CreateKey::hex2bin($key);
} catch (SodiumException $e) { } 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) { if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new \RangeException( throw new \RangeException(
'Key is not the correct size (must be ' '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); return $key;
$cipher = base64_encode(
$nonce
. sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
} }
/** /**
* Decrypt a message * Decryption call
* *
* @param string $encrypted Message encrypted with safeEncrypt() * @param string $encrypted Text to decrypt
* @param string $key Encryption key (as hex string) * @param ?string $key Mandatory encryption key, will throw exception if empty
* @return string * @return string Plain text
* @throws \Exception * @throws \RangeException
* @throws \UnexpectedValueException
* @throws \UnexpectedValueException
*/ */
public static function decrypt(string $encrypted, string $key): string private function decryptData(string $encrypted, ?string $key): string
{ {
try { if (empty($key)) {
$key = CreateKey::hex2bin($key); throw new \UnexpectedValueException('Key not set');
} catch (SodiumException $e) {
throw new \Exception('Invalid hex key');
} }
$key = $this->createKey($key);
$decoded = base64_decode($encrypted); $decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
@@ -85,7 +109,7 @@ class SymmetricEncryption
$key $key
); );
} catch (SodiumException $e) { } catch (SodiumException $e) {
throw new \UnexpectedValueException('Invalid ciphertext (too short)'); throw new \UnexpectedValueException('Decipher message failed: ' . $e->getMessage());
} }
if (!is_string($plain)) { if (!is_string($plain)) {
throw new \UnexpectedValueException('Invalid Key'); throw new \UnexpectedValueException('Invalid Key');
@@ -94,6 +118,117 @@ class SymmetricEncryption
sodium_memzero($key); sodium_memzero($key);
return $plain; 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__ // __END__