Skip to content

Feature/security helper #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/Libraries/Security.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/**
* PHP version 8
*
* @category Library
* @package Libraries
* @author Made Mas Adi Winata <m45adiwinata@gmail.com>
* @license https://mit-license.org/ MIT License
* @version GIT: 0.5.0
* @link https://github.com/spotlibs
*/

declare(strict_types=1);

namespace Spotlibs\PhpLib\Libraries;

/**
* Security
*
* Security helper
*
* @category Library
* @package Security
* @author Made Mas Adi Winata <m45adiwinata@gmail.com>
* @license https://mit-license.org/ MIT License
* @link https://github.com/spotlibs
*/
class Security
{
/**
* Encrypting sensitive string data
*
* @param string $plaintext string to encrypt
*
* @throws \Exception
* @return bool|string
*/
public static function encrypt(string $plaintext): string
{
$charset = array_merge(
range('0', '9'),
range('a', 'z'),
range('A', 'Z'),
);
$ivArr = [];
for ($i = 0; $i < 16; $i++) {
$ivArr[] = $charset[random_int(0, 61)];
}
$iv = implode('', $ivArr);
$ecrypted = openssl_encrypt($plaintext, "AES-128-CBC", env('SECURITY_KEY'), OPENSSL_RAW_DATA, $iv);
if (!$ecrypted) {
throw new \Exception("failed to encrypt string");
}
return bin2hex($iv . $ecrypted);
}

/**
* Decrypt encrypted string
*
* @param string $encrypted string to decrypt
*
* @throws \Exception
* @return bool|string
*/
public static function decrypt(string $encrypted): string
{

$ivHex = substr($encrypted, 0, 32);
$iv = hex2bin($ivHex);
$encrypted = substr($encrypted, 32);
$decrypted = openssl_decrypt(hex2bin($encrypted), "AES-128-CBC", env('SECURITY_KEY'), OPENSSL_RAW_DATA, $iv);
if (!$decrypted) {
throw new \Exception("failed to decrypt string");
}
return $decrypted;
}
}
19 changes: 16 additions & 3 deletions tests/Libraries/ClientExternalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tests\Libraries;

use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\MultipartStream;
Expand Down Expand Up @@ -31,7 +32,11 @@ public function testCallEksternal1(): void
['content-type' => 'application/json'],
json_encode(['message' => 'hello world'])
);
$client = new ClientExternal();
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], json_encode(['status' => 'ok', 'message' => 'hello world'])),
]);
$handlerStack = new HandlerStack($mock);
$client = new ClientExternal(['handler' => $handlerStack]);
$response = $client->call($request);
$contents = $response->getBody()->getContents();
$contents_arr = json_decode($contents, true, 512);
Expand All @@ -50,6 +55,11 @@ public function testCallExternalMultipartSuccess(): void
$f = fopen('public/docs/hello.txt', 'w');
fwrite($f, 'hello world');
fclose($f);
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], json_encode(['id' => '101', 'status' => 'ok', 'message' => 'well done'])),
]);
$handlerStack = new HandlerStack($mock);
$client = new ClientExternal(['handler' => $handlerStack]);
$request = new Request(
'POST',
'https://jsonplaceholder.typicode.com/posts',
Expand All @@ -61,7 +71,6 @@ public function testCallExternalMultipartSuccess(): void
]
])
);
$client = new ClientExternal();
$resp = $client
->injectRequestHeader(['X-Unit-Test' => ['clover']])
->injectResponseHeader(['X-Unit-Test-Response' => ['clover-response']])
Expand All @@ -88,7 +97,11 @@ public function testCallExternalMultipartError(): void
]
])
);
$client = new ClientExternal();
$mock = new MockHandler([
new ConnectException('simulated error', $request)
]);
$handlerStack = new HandlerStack($mock);
$client = new ClientExternal(['handler' => $handlerStack]);
$client->call($request);
}

Expand Down
42 changes: 42 additions & 0 deletions tests/Libraries/SecurityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Tests\Libraries;

use Laravel\Lumen\Testing\TestCase;
use Spotlibs\PhpLib\Libraries\Security;

class SecurityTest extends TestCase
{
public function createApplication()
{
return require __DIR__.'/../../bootstrap/app.php';
}

public function testEncrypt1(): void
{
putenv('SECURITY_KEY=123456789ABCDefg');
$plain = 'beautiful soup';
$encrypted = Security::encrypt($plain);
$decrypted = Security::decrypt($encrypted);
$this->assertEquals($plain, $decrypted);
}

public function testDecrypt1(): void
{
putenv('SECURITY_KEY=0123456789abcdef');
$encrypted = '69687168694E496177653970746B6834383021D52B533A55ECBA5BACC753055AD59F65DD091541A32FA262B3116CFDC3';
$decrypted = Security::decrypt($encrypted);
$this->assertEquals('AES CBC with secure random IV', $decrypted);
}

public function testEncryptError(): void
{
$this->expectException(\Exception::class);
putenv('SECURITY_KEY=0123456789abcd');
$x = Security::encrypt('beautiful soup');
putenv('SECURITY_KEY=0123456789abcd321');
Security::decrypt($x);
}
}