- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
21 KiB
21 KiB
Cryptography Module - Praktische Beispiele
Umfassende Sammlung praktischer Anwendungsfälle für das Cryptography Module.
Authentifizierung und Session-Management
User Password System
final readonly class UserAuthenticationService
{
public function __construct(
private KeyDerivationFunction $kdf,
private SecureTokenGenerator $tokenGenerator,
private UserRepository $userRepository
) {}
public function registerUser(string $email, string $password): User
{
// Password hashen mit Argon2ID
$hashedPassword = $this->kdf->hashPassword($password, 'argon2id', [
'memory_cost' => 65536, // 64 MB
'time_cost' => 4, // 4 Iterationen
'threads' => 3 // 3 Threads
]);
$user = new User(
id: Ulid::generate(),
email: new Email($email),
passwordHash: $hashedPassword,
createdAt: new DateTimeImmutable()
);
return $this->userRepository->save($user);
}
public function authenticateUser(string $email, string $password): ?SessionToken
{
$user = $this->userRepository->findByEmail(new Email($email));
if (!$user || !$this->kdf->verify($password, $user->getPasswordHash())) {
// Timing-Angriffe verhindern durch konstante Ausführungszeit
$this->kdf->hashPassword('dummy-password', 'argon2id');
return null;
}
// Session Token generieren
$sessionToken = $this->tokenGenerator->generateSessionToken(48); // 384 Bit
// In Session Store speichern
$this->storeSessionToken($user, $sessionToken);
return $sessionToken;
}
private function storeSessionToken(User $user, SecureToken $token): void
{
$session = new UserSession(
tokenHash: hash('sha256', $token->getValue()),
userId: $user->getId(),
expiresAt: (new DateTimeImmutable())->add(new DateInterval('PT24H')),
metadata: $token->getMetadata()
);
// Session in Datenbank speichern (nur Hash, nie Klartext!)
$this->sessionRepository->save($session);
}
}
API Key Management System
final readonly class ApiKeyManagementService
{
public function __construct(
private SecureTokenGenerator $tokenGenerator,
private CryptographicUtilities $utils,
private ApiKeyRepository $repository
) {}
public function createApiKey(
User $user,
string $name,
array $scopes = [],
?DateTimeImmutable $expiresAt = null
): ApiKeyResult {
// API Key mit Prefix generieren
$apiKey = $this->tokenGenerator->generateApiKey(
prefix: 'ak',
length: 32
);
// API Key Entity erstellen (Hash speichern, nicht Klartext)
$apiKeyEntity = new ApiKey(
id: Ulid::generate(),
userId: $user->getId(),
name: $name,
keyHash: hash('sha256', $apiKey->getValue()),
fingerprint: $apiKey->getFingerprint(),
scopes: $scopes,
expiresAt: $expiresAt,
createdAt: new DateTimeImmutable()
);
$this->repository->save($apiKeyEntity);
return new ApiKeyResult(
apiKey: $apiKey->getValue(), // Nur einmal zurückgeben
keyId: $apiKeyEntity->getId(),
fingerprint: $apiKey->getShortFingerprint(),
expiresAt: $expiresAt
);
}
public function validateApiKey(string $apiKey): ?ApiKey
{
$keyHash = hash('sha256', $apiKey);
$storedKey = $this->repository->findByHash($keyHash);
if (!$storedKey) {
return null;
}
// Timing-sicherer Vergleich
if (!$this->utils->timingSafeEquals($keyHash, $storedKey->getKeyHash())) {
return null;
}
// Expiration prüfen
if ($storedKey->getExpiresAt() && $storedKey->getExpiresAt() < new DateTimeImmutable()) {
return null;
}
return $storedKey;
}
public function rotateApiKey(string $currentApiKey): ApiKeyResult
{
$oldKey = $this->validateApiKey($currentApiKey);
if (!$oldKey) {
throw new ApiKeyException('Invalid API key for rotation');
}
// Neuen Key mit gleichen Eigenschaften erstellen
$newApiKey = $this->createApiKey(
user: $oldKey->getUser(),
name: $oldKey->getName() . ' (rotated)',
scopes: $oldKey->getScopes(),
expiresAt: $oldKey->getExpiresAt()
);
// Alten Key deaktivieren (nicht löschen für Audit-Trail)
$oldKey->deactivate();
$this->repository->save($oldKey);
return $newApiKey;
}
}
CSRF Protection
CSRF Token System
final readonly class CsrfProtectionService
{
public function __construct(
private SecureTokenGenerator $tokenGenerator,
private CacheInterface $cache
) {}
public function generateCsrfToken(string $sessionId): SecureToken
{
$csrfToken = $this->tokenGenerator->generateCsrfToken();
// Token mit Session verknüpfen
$cacheKey = "csrf_token_{$sessionId}";
$this->cache->set($cacheKey, $csrfToken->getValue(), 3600); // 1 Stunde
return $csrfToken;
}
public function validateCsrfToken(string $sessionId, string $providedToken): bool
{
$cacheKey = "csrf_token_{$sessionId}";
$storedToken = $this->cache->get($cacheKey);
if (!$storedToken) {
return false;
}
// Timing-sicherer Vergleich
$isValid = hash_equals($storedToken, $providedToken);
// Token nach Verwendung löschen (Single-Use)
if ($isValid) {
$this->cache->delete($cacheKey);
}
return $isValid;
}
}
// CSRF Middleware
final readonly class CsrfProtectionMiddleware
{
public function __construct(
private CsrfProtectionService $csrfService
) {}
public function handle(HttpRequest $request, RequestHandler $handler): HttpResponse
{
// Nur bei state-changing Operations prüfen
if (in_array($request->method, [Method::POST, Method::PUT, Method::DELETE, Method::PATCH])) {
$sessionId = $request->session->getId();
$csrfToken = $request->parsedBody->get('_token') ?? $request->headers->get('X-CSRF-Token');
if (!$csrfToken || !$this->csrfService->validateCsrfToken($sessionId, $csrfToken)) {
throw new CsrfTokenMismatchException('CSRF token mismatch');
}
}
return $handler->handle($request);
}
}
Webhook Security
Webhook Signature System
final readonly class WebhookSecurityService
{
public function __construct(
private SecureTokenGenerator $tokenGenerator,
private CryptographicUtilities $utils
) {}
public function generateWebhookSecret(): SecureToken
{
return $this->tokenGenerator->generateWebhookToken();
}
public function signWebhookPayload(string $payload, SecureToken $secret): string
{
$signature = hash_hmac('sha256', $payload, $secret->getValue());
return 'sha256=' . $signature;
}
public function validateWebhookSignature(
string $payload,
string $signature,
SecureToken $secret
): bool {
$expectedSignature = $this->signWebhookPayload($payload, $secret);
// Timing-sicherer Vergleich
return $this->utils->timingSafeEquals($signature, $expectedSignature);
}
}
// Webhook Handler
final readonly class PaymentWebhookHandler
{
public function __construct(
private WebhookSecurityService $security,
private WebhookConfigRepository $configRepository
) {}
#[Route(path: '/webhooks/payment', method: Method::POST)]
public function handlePaymentWebhook(HttpRequest $request): JsonResult
{
$signature = $request->headers->get('X-Hub-Signature-256');
$payload = $request->rawBody();
if (!$signature) {
throw new WebhookException('Missing signature header');
}
// Webhook Secret für den Sender abrufen
$senderId = $request->headers->get('X-Sender-ID');
$config = $this->configRepository->findBySenderId($senderId);
if (!$config) {
throw new WebhookException('Unknown sender');
}
// Signatur validieren
if (!$this->security->validateWebhookSignature($payload, $signature, $config->getSecret())) {
throw new WebhookException('Invalid signature');
}
// Payload verarbeiten
$data = json_decode($payload, true);
$this->processPaymentEvent($data);
return new JsonResult(['status' => 'processed']);
}
}
Two-Factor Authentication (2FA)
OTP-basiertes 2FA System
final readonly class TwoFactorAuthService
{
public function __construct(
private SecureTokenGenerator $tokenGenerator,
private CacheInterface $cache,
private NotificationService $notificationService
) {}
public function generateOtpCode(User $user): string
{
$otpToken = $this->tokenGenerator->generateOtpToken(6); // 6-stelliger Code
// OTP mit User-ID und Expiration speichern
$cacheKey = "otp_{$user->getId()}";
$otpData = [
'code' => $otpToken->getValue(),
'attempts' => 0,
'created_at' => time()
];
$this->cache->set($cacheKey, $otpData, 300); // 5 Minuten gültig
return $otpToken->getValue();
}
public function sendOtpCode(User $user): void
{
$otpCode = $this->generateOtpCode($user);
// SMS oder Email versenden
$this->notificationService->sendOtpCode(
recipient: $user->getPhoneNumber() ?? $user->getEmail(),
code: $otpCode,
expiresInMinutes: 5
);
}
public function verifyOtpCode(User $user, string $providedCode): bool
{
$cacheKey = "otp_{$user->getId()}";
$otpData = $this->cache->get($cacheKey);
if (!$otpData) {
return false; // Kein OTP vorhanden oder abgelaufen
}
// Rate-Limiting: Max 3 Versuche
if ($otpData['attempts'] >= 3) {
$this->cache->delete($cacheKey);
return false;
}
// Timing-sicherer Vergleich
$isValid = hash_equals($otpData['code'], $providedCode);
if ($isValid) {
// Erfolgreiche Verifikation - OTP löschen
$this->cache->delete($cacheKey);
} else {
// Fehlversuch - Counter erhöhen
$otpData['attempts']++;
$this->cache->set($cacheKey, $otpData, 300);
}
return $isValid;
}
}
Digitale Signaturen für Dokumente
Document Signing Service
final readonly class DocumentSigningService
{
public function __construct(
private DigitalSignature $signature,
private DocumentRepository $documentRepository
) {}
public function generateSigningKeyPair(): KeyPair
{
// RSA 4096 Bit für langfristige Dokumentensignaturen
return $this->signature->generateRsaKeyPair(4096);
}
public function signDocument(
Document $document,
PrivateKey $privateKey,
User $signer
): SignedDocument {
$documentContent = $document->getContent();
$metadata = [
'document_id' => $document->getId(),
'signer_id' => $signer->getId(),
'signed_at' => (new DateTimeImmutable())->format('c'),
'algorithm' => 'RSA-SHA256'
];
// Dokument + Metadaten signieren
$dataToSign = $documentContent . json_encode($metadata);
$signatureResult = $this->signature->sign($dataToSign, $privateKey, 'sha256');
$signedDocument = new SignedDocument(
originalDocument: $document,
signature: $signatureResult,
signerPublicKey: $privateKey->getPublicKey(),
metadata: $metadata,
signedAt: new DateTimeImmutable()
);
return $this->documentRepository->saveSignedDocument($signedDocument);
}
public function verifyDocumentSignature(SignedDocument $signedDocument): bool
{
$document = $signedDocument->getOriginalDocument();
$metadata = $signedDocument->getMetadata();
$dataToVerify = $document->getContent() . json_encode($metadata);
return $this->signature->verify(
$dataToVerify,
$signedDocument->getSignature(),
$signedDocument->getSignerPublicKey()
);
}
public function createSignatureChain(array $documents, array $signers): SignatureChain
{
$chain = new SignatureChain();
foreach ($documents as $index => $document) {
$signer = $signers[$index];
$signedDocument = $this->signDocument($document, $signer->getPrivateKey(), $signer);
$chain->addSignedDocument($signedDocument);
}
// Chain-Signatur erstellen (Hash der gesamten Kette)
$chainHash = $chain->calculateChainHash();
$chainSignature = $this->signature->sign($chainHash, $signers[0]->getPrivateKey());
$chain->setChainSignature($chainSignature);
return $chain;
}
}
Passwort-Reset System
Secure Password Reset
final readonly class PasswordResetService
{
public function __construct(
private SecureTokenGenerator $tokenGenerator,
private KeyDerivationFunction $kdf,
private CacheInterface $cache,
private EmailService $emailService
) {}
public function initiatePasswordReset(User $user): void
{
// Sicheren Reset-Token generieren
$resetToken = $this->tokenGenerator->generateVerificationToken('password_reset');
// Token hashen und speichern (nie Klartext speichern)
$tokenHash = hash('sha256', $resetToken->getValue());
$resetData = [
'user_id' => $user->getId(),
'token_hash' => $tokenHash,
'created_at' => time(),
'used' => false
];
// 2 Stunden gültig
$cacheKey = "password_reset_{$user->getId()}";
$this->cache->set($cacheKey, $resetData, 7200);
// Reset-Link per Email senden
$resetLink = "https://app.example.com/reset-password?token=" .
urlencode($resetToken->getValue());
$this->emailService->send(
to: $user->getEmail(),
subject: 'Password Reset Request',
template: 'password_reset',
data: ['reset_link' => $resetLink, 'expires_in' => '2 hours']
);
}
public function validateResetToken(string $token): ?User
{
$tokenHash = hash('sha256', $token);
// Token in allen aktiven Reset-Anfragen suchen
$cacheKeys = $this->cache->getKeysByPattern('password_reset_*');
foreach ($cacheKeys as $cacheKey) {
$resetData = $this->cache->get($cacheKey);
if ($resetData &&
!$resetData['used'] &&
hash_equals($resetData['token_hash'], $tokenHash)) {
return $this->userRepository->find($resetData['user_id']);
}
}
return null;
}
public function resetPassword(string $token, string $newPassword): bool
{
$user = $this->validateResetToken($token);
if (!$user) {
return false;
}
// Neues Password hashen
$hashedPassword = $this->kdf->hashPassword($newPassword, 'argon2id');
// User Password aktualisieren
$user->updatePassword($hashedPassword);
$this->userRepository->save($user);
// Reset-Token als verwendet markieren
$cacheKey = "password_reset_{$user->getId()}";
$resetData = $this->cache->get($cacheKey);
if ($resetData) {
$resetData['used'] = true;
$this->cache->set($cacheKey, $resetData, 7200);
}
return true;
}
}
Daten-Verschlüsselung mit Schlüssel-Ableitung
Encrypted Data Storage
final readonly class EncryptedDataService
{
public function __construct(
private KeyDerivationFunction $kdf,
private CryptographicUtilities $utils,
private EncryptionService $encryption
) {}
public function encryptPersonalData(
array $personalData,
string $userPassword
): EncryptedPersonalData {
// Benutzer-spezifischen Schlüssel aus Password ableiten
$salt = $this->utils->generateNonce(32);
$derivedKey = $this->kdf->pbkdf2($userPassword, $salt, 100000, 32);
// Daten serialisieren und verschlüsseln
$serializedData = json_encode($personalData);
$encryptedData = $this->encryption->encrypt($serializedData, $derivedKey->getKey());
return new EncryptedPersonalData(
encryptedData: $encryptedData,
salt: $salt,
keyDerivation: [
'algorithm' => $derivedKey->getAlgorithm(),
'iterations' => $derivedKey->getIterations()
]
);
}
public function decryptPersonalData(
EncryptedPersonalData $encryptedData,
string $userPassword
): array {
// Gleichen Schlüssel aus Password ableiten
$derivedKey = $this->kdf->pbkdf2(
$userPassword,
$encryptedData->getSalt(),
$encryptedData->getKeyDerivation()['iterations'],
32
);
// Daten entschlüsseln
$decryptedData = $this->encryption->decrypt(
$encryptedData->getEncryptedData(),
$derivedKey->getKey()
);
return json_decode($decryptedData, true);
}
}
Audit-Trail mit Kryptographischen Fingerprints
Secure Audit Trail
final readonly class AuditTrailService
{
public function __construct(
private CryptographicUtilities $utils,
private DigitalSignature $signature,
private AuditLogRepository $repository
) {}
public function logUserAction(
User $user,
string $action,
array $context = []
): AuditLogEntry {
$timestamp = new DateTimeImmutable();
$logData = [
'user_id' => $user->getId(),
'action' => $action,
'context' => $context,
'timestamp' => $timestamp->format('c'),
'ip_address' => $this->getClientIpHash(),
'user_agent_hash' => $this->getUserAgentHash()
];
// Kryptographischen Fingerprint erstellen
$dataString = json_encode($logData, JSON_SORT_KEYS);
$fingerprint = hash('sha256', $dataString);
// Chain-Hash (verhindert Manipulation der Historie)
$previousEntry = $this->repository->getLatestEntry();
$chainHash = $previousEntry
? hash('sha256', $previousEntry->getChainHash() . $fingerprint)
: $fingerprint;
$auditEntry = new AuditLogEntry(
id: Ulid::generate(),
fingerprint: $fingerprint,
chainHash: $chainHash,
logData: $logData,
createdAt: $timestamp
);
return $this->repository->save($auditEntry);
}
public function verifyAuditTrailIntegrity(): bool
{
$entries = $this->repository->getAllEntriesOrdered();
$previousChainHash = null;
foreach ($entries as $entry) {
// Fingerprint verifizieren
$dataString = json_encode($entry->getLogData(), JSON_SORT_KEYS);
$expectedFingerprint = hash('sha256', $dataString);
if (!hash_equals($entry->getFingerprint(), $expectedFingerprint)) {
return false; // Entry wurde manipuliert
}
// Chain-Hash verifizieren
$expectedChainHash = $previousChainHash
? hash('sha256', $previousChainHash . $entry->getFingerprint())
: $entry->getFingerprint();
if (!hash_equals($entry->getChainHash(), $expectedChainHash)) {
return false; // Chain wurde unterbrochen
}
$previousChainHash = $entry->getChainHash();
}
return true;
}
private function getClientIpHash(): string
{
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
return hash('sha256', $clientIp); // IP nicht im Klartext speichern
}
private function getUserAgentHash(): string
{
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
return hash('sha256', $userAgent);
}
}
Diese Beispiele zeigen die praktische Anwendung des Cryptography Modules in realen Szenarien und demonstrieren Best Practices für sichere Implementierungen.