Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace App\Framework\Cryptography;
use App\Framework\Random\RandomGenerator;
use InvalidArgumentException;
/**
* Key Derivation Function Service
*
* Provides secure key derivation functions for password hashing and key stretching.
* Supports PBKDF2, Argon2ID, and scrypt algorithms.
*/
final readonly class KeyDerivationFunction
{
public function __construct(
private RandomGenerator $randomGenerator
) {
}
/**
* Generate salt for key derivation
*/
public function generateSalt(int $length = 32): string
{
if ($length < 16) {
throw new InvalidArgumentException('Salt length must be at least 16 bytes');
}
if ($length > 256) {
throw new InvalidArgumentException('Salt length cannot exceed 256 bytes');
}
return $this->randomGenerator->bytes($length);
}
/**
* Derive key using PBKDF2
*/
public function pbkdf2(
string $password,
string $salt,
int $iterations = 100000,
int $keyLength = 32,
string $algorithm = 'sha256'
): DerivedKey {
if (empty($password)) {
throw new InvalidArgumentException('Password cannot be empty');
}
if (strlen($salt) < 16) {
throw new InvalidArgumentException('Salt must be at least 16 bytes');
}
if ($iterations < 10000) {
throw new InvalidArgumentException('Iterations must be at least 10,000 for security');
}
if ($keyLength < 16 || $keyLength > 256) {
throw new InvalidArgumentException('Key length must be between 16 and 256 bytes');
}
$supportedAlgorithms = ['sha256', 'sha512', 'sha384', 'sha224'];
if (! in_array($algorithm, $supportedAlgorithms, true)) {
throw new InvalidArgumentException('Unsupported hash algorithm');
}
$derivedKey = hash_pbkdf2($algorithm, $password, $salt, $iterations, $keyLength, true);
return new DerivedKey(
key: $derivedKey,
salt: $salt,
algorithm: "pbkdf2-{$algorithm}",
iterations: $iterations,
keyLength: $keyLength
);
}
/**
* Derive key using Argon2ID (recommended)
*/
public function argon2id(
string $password,
string $salt,
int $memoryCost = 65536, // 64 MB
int $timeCost = 4,
int $threads = 3,
int $keyLength = 32
): DerivedKey {
if (empty($password)) {
throw new InvalidArgumentException('Password cannot be empty');
}
if (strlen($salt) < 16) {
throw new InvalidArgumentException('Salt must be at least 16 bytes');
}
if ($memoryCost < 1024) {
throw new InvalidArgumentException('Memory cost must be at least 1024 KB');
}
if ($timeCost < 1) {
throw new InvalidArgumentException('Time cost must be at least 1');
}
if ($threads < 1 || $threads > 24) {
throw new InvalidArgumentException('Threads must be between 1 and 24');
}
if ($keyLength < 16 || $keyLength > 256) {
throw new InvalidArgumentException('Key length must be between 16 and 256 bytes');
}
if (! function_exists('sodium_crypto_pwhash')) {
throw new InvalidArgumentException('Sodium extension required for Argon2ID');
}
if (! defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID')) {
throw new InvalidArgumentException('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID constant not available');
}
$derivedKey = sodium_crypto_pwhash(
$keyLength,
$password,
$salt,
$timeCost,
$memoryCost,
SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID
);
return new DerivedKey(
key: $derivedKey,
salt: $salt,
algorithm: 'argon2id',
iterations: $timeCost,
keyLength: $keyLength,
memoryCost: $memoryCost,
threads: $threads
);
}
/**
* Derive key using scrypt
*/
public function scrypt(
string $password,
string $salt,
int $costParameter = 16384, // N
int $blockSize = 8, // r
int $parallelization = 1, // p
int $keyLength = 32
): DerivedKey {
if (empty($password)) {
throw new InvalidArgumentException('Password cannot be empty');
}
if (strlen($salt) < 16) {
throw new InvalidArgumentException('Salt must be at least 16 bytes');
}
if ($costParameter < 1024) {
throw new InvalidArgumentException('Cost parameter must be at least 1024');
}
if ($blockSize < 1) {
throw new InvalidArgumentException('Block size must be at least 1');
}
if ($parallelization < 1) {
throw new InvalidArgumentException('Parallelization must be at least 1');
}
if ($keyLength < 16 || $keyLength > 256) {
throw new InvalidArgumentException('Key length must be between 16 and 256 bytes');
}
if (! function_exists('scrypt')) {
throw new InvalidArgumentException('scrypt function not available');
}
$derivedKey = scrypt(
$password,
$salt,
$costParameter,
$blockSize,
$parallelization,
$keyLength
);
return new DerivedKey(
key: $derivedKey,
salt: $salt,
algorithm: 'scrypt',
iterations: $costParameter,
keyLength: $keyLength,
blockSize: $blockSize,
parallelization: $parallelization
);
}
/**
* Verify password against derived key
*/
public function verify(string $password, DerivedKey $derivedKey): bool
{
try {
$verificationKey = match ($derivedKey->getAlgorithm()) {
'pbkdf2-sha256' => $this->pbkdf2(
$password,
$derivedKey->getSalt(),
$derivedKey->getIterations(),
$derivedKey->getKeyLength(),
'sha256'
),
'pbkdf2-sha512' => $this->pbkdf2(
$password,
$derivedKey->getSalt(),
$derivedKey->getIterations(),
$derivedKey->getKeyLength(),
'sha512'
),
'argon2id' => $this->argon2id(
$password,
$derivedKey->getSalt(),
$derivedKey->getMemoryCost() ?? 65536,
$derivedKey->getIterations(),
$derivedKey->getThreads() ?? 3,
$derivedKey->getKeyLength()
),
'scrypt' => $this->scrypt(
$password,
$derivedKey->getSalt(),
$derivedKey->getIterations(),
$derivedKey->getBlockSize() ?? 8,
$derivedKey->getParallelization() ?? 1,
$derivedKey->getKeyLength()
),
default => throw new InvalidArgumentException('Unsupported algorithm: ' . $derivedKey->getAlgorithm())
};
return hash_equals($derivedKey->getKey(), $verificationKey->getKey());
} catch (\Exception) {
return false;
}
}
/**
* Hash password with automatic salt generation (recommended for new passwords)
*/
public function hashPassword(
string $password,
string $algorithm = 'argon2id',
array $options = []
): DerivedKey {
$salt = $this->generateSalt(32);
return match ($algorithm) {
'pbkdf2-sha256' => $this->pbkdf2(
$password,
$salt,
$options['iterations'] ?? 100000,
$options['key_length'] ?? 32,
'sha256'
),
'pbkdf2-sha512' => $this->pbkdf2(
$password,
$salt,
$options['iterations'] ?? 100000,
$options['key_length'] ?? 32,
'sha512'
),
'argon2id' => $this->argon2id(
$password,
$salt,
$options['memory_cost'] ?? 65536,
$options['time_cost'] ?? 4,
$options['threads'] ?? 3,
$options['key_length'] ?? 32
),
'scrypt' => $this->scrypt(
$password,
$salt,
$options['cost_parameter'] ?? 16384,
$options['block_size'] ?? 8,
$options['parallelization'] ?? 1,
$options['key_length'] ?? 32
),
default => throw new InvalidArgumentException("Unsupported algorithm: {$algorithm}")
};
}
/**
* Get recommended parameters for different security levels
*/
public function getRecommendedParameters(string $algorithm, string $securityLevel = 'standard'): array
{
return match ([$algorithm, $securityLevel]) {
['pbkdf2-sha256', 'low'] => ['iterations' => 50000, 'key_length' => 32],
['pbkdf2-sha256', 'standard'] => ['iterations' => 100000, 'key_length' => 32],
['pbkdf2-sha256', 'high'] => ['iterations' => 200000, 'key_length' => 32],
['argon2id', 'low'] => ['memory_cost' => 32768, 'time_cost' => 3, 'threads' => 2, 'key_length' => 32],
['argon2id', 'standard'] => ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3, 'key_length' => 32],
['argon2id', 'high'] => ['memory_cost' => 131072, 'time_cost' => 6, 'threads' => 4, 'key_length' => 32],
['scrypt', 'low'] => ['cost_parameter' => 8192, 'block_size' => 8, 'parallelization' => 1, 'key_length' => 32],
['scrypt', 'standard'] => ['cost_parameter' => 16384, 'block_size' => 8, 'parallelization' => 1, 'key_length' => 32],
['scrypt', 'high'] => ['cost_parameter' => 32768, 'block_size' => 8, 'parallelization' => 2, 'key_length' => 32],
default => throw new InvalidArgumentException("Unsupported algorithm or security level: {$algorithm}, {$securityLevel}")
};
}
/**
* Factory method
*/
public static function create(RandomGenerator $randomGenerator): self
{
return new self($randomGenerator);
}
}