74 lines
1.9 KiB
PHP
74 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App;
|
|
|
|
use RuntimeException;
|
|
|
|
final class RegistrationSecretBox
|
|
{
|
|
private const CIPHER = 'aes-256-gcm';
|
|
|
|
public function __construct(
|
|
private readonly string $keyMaterial,
|
|
) {
|
|
}
|
|
|
|
public function isConfigured(): bool
|
|
{
|
|
return trim($this->keyMaterial) !== '';
|
|
}
|
|
|
|
public function encrypt(string $plaintext): string
|
|
{
|
|
$key = $this->normalizedKey();
|
|
$iv = random_bytes(12);
|
|
$tag = '';
|
|
$ciphertext = openssl_encrypt($plaintext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag);
|
|
|
|
if ($ciphertext === false) {
|
|
throw new RuntimeException('Das Passwort konnte nicht verschlüsselt werden.');
|
|
}
|
|
|
|
return base64_encode($iv . $tag . $ciphertext);
|
|
}
|
|
|
|
public function decrypt(string $encoded): string
|
|
{
|
|
$raw = base64_decode($encoded, true);
|
|
|
|
if ($raw === false || strlen($raw) < 28) {
|
|
throw new RuntimeException('Das gespeicherte Passwort ist beschädigt.');
|
|
}
|
|
|
|
$key = $this->normalizedKey();
|
|
$iv = substr($raw, 0, 12);
|
|
$tag = substr($raw, 12, 16);
|
|
$ciphertext = substr($raw, 28);
|
|
$plaintext = openssl_decrypt($ciphertext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag);
|
|
|
|
if ($plaintext === false) {
|
|
throw new RuntimeException('Das gespeicherte Passwort konnte nicht entschlüsselt werden.');
|
|
}
|
|
|
|
return $plaintext;
|
|
}
|
|
|
|
private function normalizedKey(): string
|
|
{
|
|
if (!$this->isConfigured()) {
|
|
throw new RuntimeException('REGISTRATION_PASSWORD_KEY ist nicht gesetzt.');
|
|
}
|
|
|
|
$trimmed = trim($this->keyMaterial);
|
|
$decoded = base64_decode($trimmed, true);
|
|
|
|
if ($decoded !== false && strlen($decoded) >= 32) {
|
|
return substr($decoded, 0, 32);
|
|
}
|
|
|
|
return hash('sha256', $trimmed, true);
|
|
}
|
|
}
|