Files
tez-utils-crypto/tests/AesEncryptionServiceTest.php
René Halberstadt 8a6b7cae06
PHP Composer / build (push) Successful in 32s
Inital commit after split from main project
2026-07-26 16:38:29 +02:00

224 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tez\Utils\Tests\Crypto;
use PHPUnit\Framework\TestCase;
use Tez\Utils\Crypto\AesCipher;
use Tez\Utils\Crypto\AesEncryptionService;
final class AesEncryptionServiceTest extends TestCase
{
/** 64 hex chars — valid AES-256 key */
private const KEY_256 = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2';
/** 48 hex chars — valid AES-192 key */
private const KEY_192 = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6';
/** 32 hex chars — valid AES-128 key */
private const KEY_128 = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4';
private AesEncryptionService $service256;
private AesEncryptionService $service192;
private AesEncryptionService $service128;
protected function setUp(): void
{
$this->service256 = new AesEncryptionService(self::KEY_256);
$this->service192 = new AesEncryptionService(self::KEY_192, AesCipher::Aes192Gcm);
$this->service128 = new AesEncryptionService(self::KEY_128, AesCipher::Aes128Gcm);
}
// -------------------------------------------------------------------------
// AesCipher enum — BackedEnumTrait helpers
// -------------------------------------------------------------------------
public function testValuesReturnsAllOpenSslCipherStrings(): void
{
$this->assertSame(
['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'],
AesCipher::values(),
);
}
public function testHasValueReturnsTrueForKnownCipher(): void
{
$this->assertTrue(AesCipher::hasValue('aes-256-gcm'));
$this->assertTrue(AesCipher::hasValue('aes-128-gcm'));
}
public function testHasValueReturnsFalseForUnknownCipher(): void
{
$this->assertFalse(AesCipher::hasValue('aes-512-gcm'));
$this->assertFalse(AesCipher::hasValue('des-ede3-cbc'));
}
public function testFindByCipherString(): void
{
$this->assertSame(AesCipher::Aes256Gcm, AesCipher::find('aes-256-gcm'));
$this->assertNull(AesCipher::find('unknown'));
}
public function testFromNameReturnsCase(): void
{
$this->assertSame(AesCipher::Aes256Gcm, AesCipher::fromName('Aes256Gcm'));
}
public function testIsOneOf(): void
{
$this->assertTrue(AesCipher::Aes256Gcm->isOneOf([AesCipher::Aes128Gcm, AesCipher::Aes256Gcm]));
$this->assertFalse(AesCipher::Aes192Gcm->isOneOf([AesCipher::Aes128Gcm, AesCipher::Aes256Gcm]));
}
// -------------------------------------------------------------------------
// AesCipher — crypto parameters
// -------------------------------------------------------------------------
public function testNonceLengthIs12ForAllVariants(): void
{
foreach (AesCipher::cases() as $cipher) {
$this->assertSame(12, $cipher->nonceLength(), $cipher->label());
}
}
public function testTagLengthIs16ForAllVariants(): void
{
foreach (AesCipher::cases() as $cipher) {
$this->assertSame(16, $cipher->tagLength(), $cipher->label());
}
}
public function testRequiredHexKeyLengths(): void
{
$this->assertSame(32, AesCipher::Aes128Gcm->requiredHexKeyLength());
$this->assertSame(48, AesCipher::Aes192Gcm->requiredHexKeyLength());
$this->assertSame(64, AesCipher::Aes256Gcm->requiredHexKeyLength());
}
// -------------------------------------------------------------------------
// Default cipher (AES-256-GCM)
// -------------------------------------------------------------------------
public function testDefaultCipherIsAes256Gcm(): void
{
$this->assertSame(AesCipher::Aes256Gcm, $this->service256->getCipher());
}
public function testEncryptAndDecryptRoundTripAes256(): void
{
$plaintext = 'super-secret-client-secret-value';
$this->assertSame($plaintext, $this->service256->decrypt($this->service256->encrypt($plaintext)));
}
public function testEncryptProducesDifferentOutputEachTime(): void
{
$this->assertNotSame(
$this->service256->encrypt('same-value'),
$this->service256->encrypt('same-value'),
);
}
public function testEncryptedOutputIsBase64(): void
{
$this->assertNotFalse(base64_decode($this->service256->encrypt('test'), true));
}
public function testEncryptEmptyString(): void
{
$this->assertSame('', $this->service256->decrypt($this->service256->encrypt('')));
}
public function testEncryptLargePayload(): void
{
$payload = str_repeat('a', 10_000);
$this->assertSame($payload, $this->service256->decrypt($this->service256->encrypt($payload)));
}
// -------------------------------------------------------------------------
// AES-192-GCM
// -------------------------------------------------------------------------
public function testCipherIsAes192GcmWhenConfigured(): void
{
$this->assertSame(AesCipher::Aes192Gcm, $this->service192->getCipher());
}
public function testEncryptAndDecryptRoundTripAes192(): void
{
$plaintext = 'secret-192';
$this->assertSame($plaintext, $this->service192->decrypt($this->service192->encrypt($plaintext)));
}
// -------------------------------------------------------------------------
// AES-128-GCM
// -------------------------------------------------------------------------
public function testCipherIsAes128GcmWhenConfigured(): void
{
$this->assertSame(AesCipher::Aes128Gcm, $this->service128->getCipher());
}
public function testEncryptAndDecryptRoundTripAes128(): void
{
$plaintext = 'another-secret';
$this->assertSame($plaintext, $this->service128->decrypt($this->service128->encrypt($plaintext)));
}
// -------------------------------------------------------------------------
// Key-length validation per cipher
// -------------------------------------------------------------------------
public function testConstructorThrowsWhenKeyTooShortForAes256(): void
{
$this->expectException(\InvalidArgumentException::class);
new AesEncryptionService('tooshort');
}
public function testConstructorThrowsWhenNonHexKeyGiven(): void
{
$this->expectException(\InvalidArgumentException::class);
new AesEncryptionService(str_repeat('zz', 32)); // 64 chars but not hex
}
public function testConstructorThrowsWhen256BitKeyUsedForAes128(): void
{
$this->expectException(\InvalidArgumentException::class);
new AesEncryptionService(self::KEY_256, AesCipher::Aes128Gcm);
}
public function testConstructorThrowsWhen128BitKeyUsedForAes256(): void
{
$this->expectException(\InvalidArgumentException::class);
new AesEncryptionService(self::KEY_128, AesCipher::Aes256Gcm);
}
public function testConstructorThrowsWhen256BitKeyUsedForAes192(): void
{
$this->expectException(\InvalidArgumentException::class);
new AesEncryptionService(self::KEY_256, AesCipher::Aes192Gcm);
}
// -------------------------------------------------------------------------
// Tamper / error cases
// -------------------------------------------------------------------------
public function testDecryptThrowsOnTamperedData(): void
{
$this->expectException(\RuntimeException::class);
$this->service256->decrypt(base64_encode(str_repeat('x', 64)));
}
public function testDecryptThrowsOnInvalidBase64(): void
{
$this->expectException(\RuntimeException::class);
$this->service256->decrypt('not-base64!!!');
}
public function testDecryptThrowsOnTooShortInput(): void
{
$this->expectException(\RuntimeException::class);
$this->service256->decrypt(base64_encode(str_repeat('x', 10)));
}
}