Initial commit

This commit is contained in:
2026-03-12 12:43:56 +01:00
commit f733dee856
137 changed files with 51192 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
<?php
/**
* AES-256-GCM encryption helper for sensitive data at rest (e.g., TOTP secrets).
*
* Requires TOTP_ENCRYPTION_KEY in .env (64 hex chars = 32 bytes).
* Format: base64(nonce + ciphertext + tag)
*/
declare(strict_types=1);
class Encryption
{
private const CIPHER = 'aes-256-gcm';
private const NONCE_LENGTH = 12;
private const TAG_LENGTH = 16;
private static ?string $key = null;
private static function getKey(): string
{
if (self::$key === null) {
$hex = env('TOTP_ENCRYPTION_KEY', '');
if (strlen($hex) !== 64 || !ctype_xdigit($hex)) {
throw new RuntimeException('TOTP_ENCRYPTION_KEY must be 64 hex chars (32 bytes)');
}
self::$key = hex2bin($hex);
}
return self::$key;
}
public static function encrypt(string $plaintext): string
{
$key = self::getKey();
$nonce = random_bytes(self::NONCE_LENGTH);
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
self::CIPHER,
$key,
OPENSSL_RAW_DATA,
$nonce,
$tag,
'',
self::TAG_LENGTH
);
if ($ciphertext === false) {
throw new RuntimeException('Encryption failed');
}
return base64_encode($nonce . $ciphertext . $tag);
}
public static function decrypt(string $encoded): string
{
$key = self::getKey();
$raw = base64_decode($encoded, true);
if ($raw === false || strlen($raw) < self::NONCE_LENGTH + self::TAG_LENGTH + 1) {
throw new RuntimeException('Invalid encrypted data');
}
$nonce = substr($raw, 0, self::NONCE_LENGTH);
$tag = substr($raw, -self::TAG_LENGTH);
$ciphertext = substr($raw, self::NONCE_LENGTH, -self::TAG_LENGTH);
$plaintext = openssl_decrypt(
$ciphertext,
self::CIPHER,
$key,
OPENSSL_RAW_DATA,
$nonce,
$tag
);
if ($plaintext === false) {
throw new RuntimeException('Decryption failed');
}
return $plaintext;
}
/**
* Zjisti, zda je hodnota sifrovana (base64 s ocekavanou delkou).
* TOTP secret je vzdy 16-32 ASCII znaku, sifrovany je base64 s nonce+tag.
*/
public static function isEncrypted(string $value): bool
{
if (strlen($value) < 40) {
return false;
}
$decoded = base64_decode($value, true);
return $decoded !== false
&& strlen($decoded) > self::NONCE_LENGTH + self::TAG_LENGTH;
}
}