Skip to content
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
3 changes: 0 additions & 3 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,6 @@ jobs:
- name: PHPStan (Src)
run: php -d memory_limit=-1 vendor/bin/phpstan analyse --level 10 src

- name: PHPStan (Test)
run: php -d memory_limit=-1 vendor/bin/phpstan analyse --level 10 tests

tests-phpunit:
runs-on: ubuntu-latest
name: PHPUnit
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@
"require": {
"php": ">=8.5",
"ext-openssl": "*",
"firebase/php-jwt": "^6.1",
"firebase/php-jwt": "^6.1 || ^7.0",
"vlucas/valitron": "^1.4"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.8",
"lion/test": "^3.2",
"lion/test": "^3.2 || ^4.0",
"phpstan/phpstan": "^2.1",
"squizlabs/php_codesniffer": "^3.11 || ^4.0",
"phpunit/phpunit": "^12.0",
"overtrue/phplint": "^9.5",
"infection/infection": "^0.29.14 || ^0.31.0"
"infection/infection": "^0.29.14 || ^0.31.0 || ^0.32.0"
},
"config": {
"allow-plugins": {
Expand Down
393 changes: 231 additions & 162 deletions composer.lock

Large diffs are not rendered by default.

216 changes: 106 additions & 110 deletions src/LionSecurity/AES.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,138 +5,152 @@
namespace Lion\Security;

use Exception;
use JsonException;
use Lion\Security\Exceptions\AESException;
use Lion\Security\Interfaces\ConfigInterface;
use Lion\Security\Interfaces\EncryptionInterface;
use Lion\Security\Interfaces\ObjectInterface;
use OpenSSLAsymmetricKey;
use Random\RandomException;
use stdClass;

/**
* It allows you to generate the configuration required for AES encryption and
* decryption, it has methods that allow you to encrypt and decrypt data with
* AES
*
* @package Lion\Security
* AES.
*/
class AES implements ConfigInterface, EncryptionInterface, ObjectInterface
{
/**
* [It's a robust encryption method with a 256-bit key size and uses Cipher
* Block Chaining (CBC) mode]
* AES-256-GCM is a highly secure symmetric encryption algorithm that combines
* the Advanced Encryption Standard (AES) with a 256-bit key and Galois/Counter
* (GCM) mode of operation, offering confidentiality (encryption) and
* authenticity (integrity) of data in a single step, making it fast and robust
* for protecting sensitive information in applications such as web security
* (HTTPS), VPNs, and secure messaging, ensuring that data is not only
* unreadable but also tamper-proof.
*
* @const AES_256_CBC
* @const AES_256_GCM
*/
public const string AES_256_CBC = 'aes-256-cbc';
public const string AES_256_GCM = 'aes-256-gcm';

/**
* [Property that stores the values of any type of execution being
* performed 'create, encode, decode']
* It represents the key property in the configuration.
*
* @var array<string, string>|stdClass $values
* @const KEY
*/
private array|stdClass $values = [];
public const string KEY = 'key';

/**
* [Property that contains the configuration defined for AES processes]
* It represents the encrypted block.
*
* @var array<string, int|string|OpenSSLAsymmetricKey> $config
* @const DATA
*/
private array $config = [];
public const string DATA = 'data';

/**
* {@inheritDoc}
* It represents the iv in the cipher block.
*
* @const IV
*/
public function config(array $config): AES
{
$this->config = $config;
public const string IV = 'iv';

return $this;
}
/**
* It represents the tag in the cipher block.
*
* @const TAG
*/
public const string TAG = 'tag';

/**
* Returns the current array/object with the generated data
* Property that stores the values of any type of execution being performed
* 'create, encode, decode'.
*
* @return array<string, string>|stdClass
* @var array<string, string>|stdClass $values
*/
private array|stdClass $values = [];

/**
* Property that contains the configuration defined for AES processes.
*
* @infection-ignore-all
* @var array{
* key: int|null|string|OpenSSLAsymmetricKey
* } $config
*/
public function get(): array|stdClass
{
$values = $this->values;
private array $config;

$this->clean();
/**
* {@inheritDoc}
*/
public function config(array $config): AES
{
$this->config = $config;

return $values;
return $this;
}

/**
* {@inheritDoc}
*
* @throws AESException
* @throws JsonException If encoding to JSON fails.
* @throws RandomException
*/
public function encode(string $key, string $value): AES
public function encode(array $data): AES
{
/** @var string $method */
$method = $this->config['method'];

/** @var string $passphrase */
$passphrase = $this->config['passphrase'];

/** @var string $hex2BinPassphrase */
$hex2BinPassphrase = hex2bin($passphrase);

/** @var string $iv */
$iv = $this->config['iv'];
$iv = random_bytes(12);

/** @var string $hex2BinIv */
$hex2BinIv = hex2bin($iv);
$tag = '';

$encrypt = openssl_encrypt($value, $method, $hex2BinPassphrase, OPENSSL_RAW_DATA, $hex2BinIv);
$plaintext = json_encode($data, JSON_THROW_ON_ERROR);

if (!$encrypt) {
/** @var string $message */
$message = openssl_error_string();
$ciphertext = openssl_encrypt(
$plaintext,
self::AES_256_GCM,
/** @phpstan-ignore-next-line */
hex2bin($this->config[self::KEY]),
OPENSSL_RAW_DATA,
$iv,
$tag
);

throw new AESException($message, 500);
if ($ciphertext === false) {
/** @phpstan-ignore-next-line */
throw new AESException(openssl_error_string(), 500);
}

if (is_array($this->values)) {
$this->values[$key] = base64_encode($encrypt);
}
$this->values = [
self::DATA => base64_encode($ciphertext),
self::IV => base64_encode($iv),
self::TAG => base64_encode($tag),
];

return $this;
}

/**
* {@inheritDoc}
*
* @throws JsonException If encoding to JSON fails.
*/
public function decode(array $rows): AES
public function decode(array $encrypted): AES
{
foreach ($rows as $key => $row) {
/** @var string $method */
$method = $this->config['method'];

/** @var string $passphrase */
$passphrase = $this->config['passphrase'];

/** @var string $hex2BinPassphrase */
$hex2BinPassphrase = hex2bin($passphrase);

/** @var string $iv */
$iv = $this->config['iv'];

/** @var string $hex2BinIv */
$hex2BinIv = hex2bin($iv);

/** @var string $decrypt */
$decrypt = openssl_decrypt(base64_decode($row), $method, $hex2BinPassphrase, OPENSSL_RAW_DATA, $hex2BinIv);

if (is_array($this->values)) {
$this->values[$key] = $decrypt;
}
$plaintext = openssl_decrypt(
base64_decode($encrypted['data']),
self::AES_256_GCM,
/** @phpstan-ignore-next-line */
hex2bin($this->config['key']),
OPENSSL_RAW_DATA,
base64_decode($encrypted['iv']),
base64_decode($encrypted['tag'])
);

if ($plaintext === false) {
throw new AESException('Authentication failed', 500);
}

/** @phpstan-ignore-next-line */
$this->values = json_decode($plaintext, true, 512, JSON_THROW_ON_ERROR);

return $this;
}

Expand All @@ -153,66 +167,48 @@ public function toObject(): AES
}

/**
* Clear variables so they have their original value
* Clear variables so they have their original value.
*
* @return void
*/
private function clean(): void
{
$this->values = [];

/** @phpstan-ignore-next-line */
$this->config = [];
}

/**
* Add formatting to the defined algorithm
* Creates key and iv for aes encryption.
*
* @param string $key [AES algorithm type]
* @return AES
*
* @return string
* @throws Exception If the algorithm is not supported.
*/
private function formatCipherKey(string $key): string
public function create(): AES
{
return trim(strtolower($key));
}
$this->values = [
'key' => bin2hex(random_bytes(32)), // 256 bits
'iv' => bin2hex(random_bytes(12)), // GCM estándar
];

/**
* Get length of certain encryption method
*
* @param string $key [AES algorithm type]
*
* @return int
*
* @throws Exception [If the algorithm is not supported]
*/
public function cipherKeyLength(string $key): int
{
return match ($this->formatCipherKey($key)) {
self::AES_256_CBC => 32,
default => throw new Exception("The algorithm is not supported", 500),
};
return $this;
}

/**
* Creates key and iv for aes encryption
*
* @param string $key [AES algorithm type]
* Returns the current array/object with the generated data.
*
* @return AES
* @return array<string, string>|stdClass
*
* @throws Exception [If the algorithm is not supported]
* @infection-ignore-all
*/
public function create(string $key): AES
public function get(): array|stdClass
{
$length = $this->cipherKeyLength($key);
$values = $this->values;

$this->values = [
'passphrase' => hash('sha256', md5(uniqid())),
'key' => bin2hex(openssl_random_pseudo_bytes($length)),
'iv' => bin2hex(openssl_random_pseudo_bytes(16)),
'method' => $this->formatCipherKey($key),
];
$this->clean();

return $this;
return $values;
}
}
14 changes: 6 additions & 8 deletions src/LionSecurity/Exceptions/AESException.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@
use Exception;

/**
* This class represents custom exceptions for AES class processes
*
* @package Lion\Security\Exceptions
* This class represents custom exceptions for AES class processes.
*/
class AESException extends Exception
{
/**
* Constructor method of the class
* Constructor method of the class.
*
* @param string $message [The Exception message to throw]
* @param int $code [The Exception code]
* @param Exception|null $previous [The previus Throwable used for the
* exception chaining]
* @param string $message The Exception message to throw.
* @param int $code The Exception code.
* @param Exception|null $previous The previus Throwable used for the
* exception chaining.
*/
public function __construct(string $message, int $code = 500, ?Exception $previous = null)
{
Expand Down
Loading