- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
703 lines
21 KiB
Markdown
703 lines
21 KiB
Markdown
# Cryptography Module - Praktische Beispiele
|
|
|
|
Umfassende Sammlung praktischer Anwendungsfälle für das Cryptography Module.
|
|
|
|
## Authentifizierung und Session-Management
|
|
|
|
### User Password System
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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
|
|
|
|
```php
|
|
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. |