Skip to content

Commit

Permalink
Merge pull request #16 from IlicMiljan/abstract-encoding-logic
Browse files Browse the repository at this point in the history
Abstract Encoding Logic
  • Loading branch information
IlicMiljan committed Mar 15, 2024
2 parents 5fb64b5 + a2744e0 commit 9c6e941
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 7 deletions.
18 changes: 15 additions & 3 deletions src/Cipher/AdvancedEncryptionStandardCipher.php
Expand Up @@ -7,6 +7,8 @@
use IlicMiljan\SecureProps\Cipher\Exception\FailedEncryptingValue;
use IlicMiljan\SecureProps\Cipher\Exception\FailedGeneratingInitializationVector;
use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength;
use IlicMiljan\SecureProps\Encoder\Base64Encoder;
use IlicMiljan\SecureProps\Encoder\Encoder;
use SensitiveParameter;

class AdvancedEncryptionStandardCipher implements Cipher
Expand All @@ -15,11 +17,20 @@ class AdvancedEncryptionStandardCipher implements Cipher
private const TAG_LENGTH = 16;
private const KEY_LENGTH = 32;

private Encoder $encoder;

public function __construct(
#[SensitiveParameter]
private string $key
private string $key,
?Encoder $encoder = null
) {
$this->validateKey($key);

if ($encoder === null) {
$this->encoder = new Base64Encoder();
} else {
$this->encoder = $encoder;
}
}

/**
Expand All @@ -35,15 +46,16 @@ public function encrypt(#[SensitiveParameter] string $string): string
throw new FailedEncryptingValue();
}

return base64_encode($iv . $encryptedString . $tag);
return $this->encoder->encode($iv . $encryptedString . $tag);
}

/**
* @inheritDoc
*
*/
public function decrypt(#[SensitiveParameter] string $string): string
{
$data = base64_decode($string);
$data = $this->encoder->decode($string);

$ivLength = $this->calculateInitializationVectorLength(self::CIPHER);

Expand Down
16 changes: 13 additions & 3 deletions src/Cipher/AsymmetricEncryptionCipher.php
Expand Up @@ -4,14 +4,24 @@

use IlicMiljan\SecureProps\Cipher\Exception\FailedDecryptingValue;
use IlicMiljan\SecureProps\Cipher\Exception\FailedEncryptingValue;
use IlicMiljan\SecureProps\Encoder\Base64Encoder;
use IlicMiljan\SecureProps\Encoder\Encoder;
use SensitiveParameter;

class AsymmetricEncryptionCipher implements Cipher
{
private Encoder $encoder;

public function __construct(
private string $publicKey,
private string $privateKey
private string $privateKey,
?Encoder $encoder = null
) {
if ($encoder === null) {
$this->encoder = new Base64Encoder();
} else {
$this->encoder = $encoder;
}
}

/**
Expand All @@ -23,15 +33,15 @@ public function encrypt(#[SensitiveParameter] string $string): string
throw new FailedEncryptingValue();
}

return base64_encode($encrypted);
return $this->encoder->encode($encrypted);
}

/**
* @inheritDoc
*/
public function decrypt(#[SensitiveParameter] string $string): string
{
$data = base64_decode($string);
$data = $this->encoder->decode($string);

if (!openssl_private_decrypt($data, $decrypted, $this->privateKey)) {
throw new FailedDecryptingValue();
Expand Down
24 changes: 24 additions & 0 deletions src/Encoder/Base64Encoder.php
@@ -0,0 +1,24 @@
<?php

namespace IlicMiljan\SecureProps\Encoder;

use SensitiveParameter;

class Base64Encoder implements Encoder
{
/**
* @inheritDoc
*/
public function encode(#[SensitiveParameter] string $value): string
{
return base64_encode($value);
}

/**
* @inheritDoc
*/
public function decode(#[SensitiveParameter] string $value): string
{
return base64_decode($value);
}
}
38 changes: 38 additions & 0 deletions src/Encoder/Encoder.php
@@ -0,0 +1,38 @@
<?php

namespace IlicMiljan\SecureProps\Encoder;

use SensitiveParameter;

/**
* Encodes and decodes string values.
*
* This interface should be implemented by classes that provide mechanisms
* for encoding and decoding string values.
*
* Implementations could, for example, apply base64 encoding/decoding, or any
* other form of transformation that maintains the reversibility of the value.
*/
interface Encoder
{
/**
* Encodes the provided string value.
*
* Takes a string as input and returns an encoded version of the string.f
*
* @param string $value The string value to encode.
* @return string The encoded string.
*/
public function encode(#[SensitiveParameter] string $value): string;

/**
* Decodes the provided encoded string value.
*
* Takes an encoded string as input and returns the original unencoded
* version of the string.
*
* @param string $value The encoded string to decode.
* @return string The original, unencoded string.
*/
public function decode(#[SensitiveParameter] string $value): string;
}
24 changes: 24 additions & 0 deletions src/Encoder/NullEncoder.php
@@ -0,0 +1,24 @@
<?php

namespace IlicMiljan\SecureProps\Encoder;

use SensitiveParameter;

class NullEncoder implements Encoder
{
/**
* @inheritDoc
*/
public function encode(#[SensitiveParameter] string $value): string
{
return $value;
}

/**
* @inheritDoc
*/
public function decode(#[SensitiveParameter] string $value): string
{
return $value;
}
}
26 changes: 26 additions & 0 deletions tests/Attribute/SensitiveParameterTest.php
@@ -0,0 +1,26 @@
<?php

namespace IlicMiljan\SecureProps\Tests\Attribute;

use IlicMiljan\SecureProps\Attribute\Encrypted;
use PHPUnit\Framework\TestCase;
use SensitiveParameter;

class SensitiveParameterTest extends TestCase
{
protected function setUp(): void
{
if (PHP_VERSION_ID >= 80200) {
$this->markTestSkipped(
'All tests in this file have been skipped because they are do not apply to PHP 8.2 or higher.'
);
}
}

public function testCanBeCreated(): void
{
$encrypted = new SensitiveParameter();

$this->assertInstanceOf(SensitiveParameter::class, $encrypted);
}
}
39 changes: 38 additions & 1 deletion tests/Cipher/AdvancedEncryptionStandardCipherTest.php
Expand Up @@ -3,16 +3,31 @@
namespace IlicMiljan\SecureProps\Tests\Cipher;

use IlicMiljan\SecureProps\Cipher\AdvancedEncryptionStandardCipher;
use IlicMiljan\SecureProps\Cipher\Exception\CipherException;
use IlicMiljan\SecureProps\Cipher\Exception\InvalidKeyLength;
use IlicMiljan\SecureProps\Encoder\Encoder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class AdvancedEncryptionStandardCipherTest extends TestCase
{
/** @var Encoder&MockObject */
private $encoder;
private AdvancedEncryptionStandardCipher $cipher;
private AdvancedEncryptionStandardCipher $cipherWithCustomEncoder;

protected function setUp(): void
{
$this->cipher = new AdvancedEncryptionStandardCipher(openssl_random_pseudo_bytes(32));
$this->encoder = $this->createMock(Encoder::class);

$this->cipher = new AdvancedEncryptionStandardCipher(
openssl_random_pseudo_bytes(32),
);

$this->cipherWithCustomEncoder = new AdvancedEncryptionStandardCipher(
openssl_random_pseudo_bytes(32),
$this->encoder
);
}

public function testConstructWithInvalidKeyLengthThrowsException(): void
Expand All @@ -22,11 +37,33 @@ public function testConstructWithInvalidKeyLengthThrowsException(): void
new AdvancedEncryptionStandardCipher(openssl_random_pseudo_bytes(16));
}

/**
* @throws CipherException
*/
public function testEncryptAndDecryptSuccessfully(): void
{
$encryptedString = $this->cipher->encrypt('plainText');
$decryptedString = $this->cipher->decrypt($encryptedString);

$this->assertEquals('plainText', $decryptedString);
}

/**
* @throws CipherException
*/
public function testEncryptAndDecryptWithCustomEncoder(): void
{
$this->encoder->method('encode')->willReturnCallback(function ($data) {
return $data;
});

$this->encoder->method('decode')->willReturnCallback(function ($data) {
return $data;
});

$encryptedText = $this->cipherWithCustomEncoder->encrypt('plainText');
$decryptedText = $this->cipherWithCustomEncoder->decrypt($encryptedText);

$this->assertEquals('plainText', $decryptedText);
}
}
36 changes: 36 additions & 0 deletions tests/Cipher/AsymmetricEncryptionCipherTest.php
Expand Up @@ -3,15 +3,23 @@
namespace IlicMiljan\SecureProps\Tests\Cipher;

use IlicMiljan\SecureProps\Cipher\AsymmetricEncryptionCipher;
use IlicMiljan\SecureProps\Cipher\Exception\CipherException;
use IlicMiljan\SecureProps\Encoder\Encoder;
use OpenSSLAsymmetricKey;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class AsymmetricEncryptionCipherTest extends TestCase
{
/** @var Encoder&MockObject */
private $encoder;
private AsymmetricEncryptionCipher $cipher;
private AsymmetricEncryptionCipher $cipherWithCustomEncoder;

protected function setUp(): void
{
$this->encoder = $this->createMock(Encoder::class);

/** @var OpenSSLAsymmetricKey $asymmetricKey */
$asymmetricKey = openssl_pkey_new();
/** @var string[] $asymmetricKeyDetails */
Expand All @@ -20,13 +28,41 @@ protected function setUp(): void
openssl_pkey_export($asymmetricKey, $privateKey);

$this->cipher = new AsymmetricEncryptionCipher($asymmetricKeyDetails['key'], $privateKey);

$this->cipherWithCustomEncoder = new AsymmetricEncryptionCipher(
$asymmetricKeyDetails['key'],
$privateKey,
$this->encoder
);
}

/**
* @throws CipherException
*/
public function testEncryptAndDecryptSuccessfully(): void
{
$encryptedString = $this->cipher->encrypt('plainText');
$decryptedString = $this->cipher->decrypt($encryptedString);

$this->assertEquals('plainText', $decryptedString);
}

/**
* @throws CipherException
*/
public function testEncryptAndDecryptWithCustomEncoder(): void
{
$this->encoder->method('encode')->willReturnCallback(function ($data) {
return $data;
});

$this->encoder->method('decode')->willReturnCallback(function ($data) {
return $data;
});

$encryptedText = $this->cipherWithCustomEncoder->encrypt('plainText');
$decryptedText = $this->cipherWithCustomEncoder->decrypt($encryptedText);

$this->assertEquals('plainText', $decryptedText);
}
}
52 changes: 52 additions & 0 deletions tests/Encoder/Base64EncoderTest.php
@@ -0,0 +1,52 @@
<?php

namespace IlicMiljan\SecureProps\Tests\Encoder;

use IlicMiljan\SecureProps\Encoder\Base64Encoder;
use PHPUnit\Framework\TestCase;

class Base64EncoderTest extends TestCase
{
private Base64Encoder $encoder;

protected function setUp(): void
{
$this->encoder = new Base64Encoder();
}

public function testEncode(): void
{
$string = "Hello, World!";
$expectedEncodedString = base64_encode($string);

$this->assertEquals(
$expectedEncodedString,
$this->encoder->encode($string),
"The encoded string does not match the expected output."
);
}

public function testDecode(): void
{
$encodedString = "SGVsbG8sIFdvcmxkIQ==";
$expectedDecodedString = base64_decode($encodedString);

$this->assertEquals(
$expectedDecodedString,
$this->encoder->decode($encodedString)
);
}

public function testEncodeDecode(): void
{
$originalString = "Test encode and decode!";

$encodedString = $this->encoder->encode($originalString);
$decodedString = $this->encoder->decode($encodedString);

$this->assertEquals(
$originalString,
$decodedString
);
}
}

0 comments on commit 9c6e941

Please sign in to comment.