getKey())->toBe($key); expect($derivedKey->getSalt())->toBe($salt); expect($derivedKey->getAlgorithm())->toBe('pbkdf2-sha256'); expect($derivedKey->getIterations())->toBe(100000); expect($derivedKey->getKeyLength())->toBe(32); }); it('throws exception for empty key', function () { expect(fn () => new DerivedKey( key: '', salt: str_repeat('b', 32), algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ))->toThrow(InvalidArgumentException::class, 'Key cannot be empty'); }); it('throws exception for empty salt', function () { expect(fn () => new DerivedKey( key: str_repeat('a', 32), salt: '', algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ))->toThrow(InvalidArgumentException::class, 'Salt cannot be empty'); }); it('throws exception for key length mismatch', function () { expect(fn () => new DerivedKey( key: str_repeat('a', 16), // 16 bytes salt: str_repeat('b', 32), algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 // Claims 32 bytes ))->toThrow(InvalidArgumentException::class, 'Key length does not match specified length'); }); it('provides hex representation', function () { $key = "\x01\x02\x03\x04"; $salt = "\x05\x06\x07\x08"; $derivedKey = new DerivedKey( key: $key, salt: $salt, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 4 ); expect($derivedKey->getKeyHex())->toBe('01020304'); expect($derivedKey->getSaltHex())->toBe('05060708'); }); it('provides base64 representation', function () { $key = 'test-key-data'; $salt = 'test-salt-data'; $derivedKey = new DerivedKey( key: $key, salt: $salt, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: strlen($key) ); expect($derivedKey->getKeyBase64())->toBe(base64_encode($key)); expect($derivedKey->getSaltBase64())->toBe(base64_encode($salt)); }); it('checks equality correctly', function () { $key = str_repeat('a', 32); $salt = str_repeat('b', 32); $derivedKey1 = new DerivedKey( key: $key, salt: $salt, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); $derivedKey2 = new DerivedKey( key: $key, salt: $salt, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); $derivedKey3 = new DerivedKey( key: str_repeat('c', 32), // Different key salt: $salt, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); expect($derivedKey1->equals($derivedKey2))->toBeTrue(); expect($derivedKey1->equals($derivedKey3))->toBeFalse(); }); it('exports to array correctly', function () { $derivedKey = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'argon2id', iterations: 4, keyLength: 32, memoryCost: 65536, threads: 3 ); $array = $derivedKey->toArray(); expect($array)->toHaveKey('key'); expect($array)->toHaveKey('salt'); expect($array)->toHaveKey('algorithm'); expect($array)->toHaveKey('iterations'); expect($array)->toHaveKey('key_length'); expect($array)->toHaveKey('memory_cost'); expect($array)->toHaveKey('threads'); expect($array['algorithm'])->toBe('argon2id'); expect($array['memory_cost'])->toBe(65536); expect($array['threads'])->toBe(3); }); it('creates from array correctly', function () { $originalKey = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); $array = $originalKey->toArray(); $restoredKey = DerivedKey::fromArray($array); expect($restoredKey->equals($originalKey))->toBeTrue(); }); it('creates from hex correctly', function () { $keyHex = '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20'; $saltHex = 'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef'; $derivedKey = DerivedKey::fromHex( keyHex: $keyHex, saltHex: $saltHex, algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); expect($derivedKey->getKeyHex())->toBe($keyHex); expect($derivedKey->getSaltHex())->toBe($saltHex); }); it('provides summary information', function () { $derivedKey = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 16), algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); $summary = $derivedKey->getSummary(); expect($summary)->toHaveKey('algorithm'); expect($summary)->toHaveKey('iterations'); expect($summary)->toHaveKey('key_length'); expect($summary)->toHaveKey('salt_length'); expect($summary['salt_length'])->toBe(16); }); it('identifies algorithm types correctly', function () { $pbkdf2Key = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'pbkdf2-sha256', iterations: 100000, keyLength: 32 ); $argon2Key = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'argon2id', iterations: 4, keyLength: 32 ); $scryptKey = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'scrypt', iterations: 16384, keyLength: 32 ); expect($pbkdf2Key->isPbkdf2())->toBeTrue(); expect($pbkdf2Key->isArgon2())->toBeFalse(); expect($pbkdf2Key->isScrypt())->toBeFalse(); expect($argon2Key->isArgon2())->toBeTrue(); expect($argon2Key->isPbkdf2())->toBeFalse(); expect($scryptKey->isScrypt())->toBeTrue(); expect($scryptKey->isPbkdf2())->toBeFalse(); }); it('handles scrypt parameters correctly', function () { $derivedKey = new DerivedKey( key: str_repeat('a', 32), salt: str_repeat('b', 32), algorithm: 'scrypt', iterations: 16384, keyLength: 32, blockSize: 8, parallelization: 1 ); expect($derivedKey->getBlockSize())->toBe(8); expect($derivedKey->getParallelization())->toBe(1); }); it('throws exception for missing required fields in fromArray', function () { $incompleteData = [ 'key' => base64_encode(str_repeat('a', 32)), 'salt' => base64_encode(str_repeat('b', 32)), 'algorithm' => 'pbkdf2-sha256', // Missing iterations and key_length ]; expect(fn () => DerivedKey::fromArray($incompleteData)) ->toThrow(InvalidArgumentException::class, 'Missing required field'); }); it('throws exception for invalid base64 in fromArray', function () { $invalidData = [ 'key' => 'invalid-base64!@#', 'salt' => base64_encode(str_repeat('b', 32)), 'algorithm' => 'pbkdf2-sha256', 'iterations' => 100000, 'key_length' => 32, ]; expect(fn () => DerivedKey::fromArray($invalidData)) ->toThrow(InvalidArgumentException::class, 'Failed to decode Base64 data'); });