103 lines
2.8 KiB
PHP
103 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
use RuntimeException;
|
|
|
|
class replication_secret_box
|
|
{
|
|
private const PREFIX = 'twsec:v1:';
|
|
private const CIPHER = 'aes-256-gcm';
|
|
|
|
public static function encrypt(string $plaintext): string
|
|
{
|
|
$nonce = random_bytes(12);
|
|
$tag = '';
|
|
$ciphertext = openssl_encrypt(
|
|
$plaintext,
|
|
self::CIPHER,
|
|
self::key(),
|
|
OPENSSL_RAW_DATA,
|
|
$nonce,
|
|
$tag,
|
|
'',
|
|
16
|
|
);
|
|
|
|
if ($ciphertext === false || $tag === '') {
|
|
throw new RuntimeException('Secret encryption failed.');
|
|
}
|
|
|
|
return self::PREFIX . base64_encode(json_encode([
|
|
'nonce' => base64_encode($nonce),
|
|
'tag' => base64_encode($tag),
|
|
'ciphertext' => base64_encode($ciphertext),
|
|
], JSON_UNESCAPED_SLASHES));
|
|
}
|
|
|
|
public static function decrypt(?string $secret): string
|
|
{
|
|
$secret = (string)$secret;
|
|
if ($secret === '') {
|
|
return '';
|
|
}
|
|
|
|
if (!str_starts_with($secret, self::PREFIX)) {
|
|
return $secret;
|
|
}
|
|
|
|
$payload = json_decode(base64_decode(substr($secret, strlen(self::PREFIX)), true) ?: '', true);
|
|
if (!is_array($payload)) {
|
|
throw new RuntimeException('Encrypted secret payload is invalid.');
|
|
}
|
|
|
|
$nonce = base64_decode((string)($payload['nonce'] ?? ''), true);
|
|
$tag = base64_decode((string)($payload['tag'] ?? ''), true);
|
|
$ciphertext = base64_decode((string)($payload['ciphertext'] ?? ''), true);
|
|
|
|
if ($nonce === false || $tag === false || $ciphertext === false) {
|
|
throw new RuntimeException('Encrypted secret payload is incomplete.');
|
|
}
|
|
|
|
$plaintext = openssl_decrypt(
|
|
$ciphertext,
|
|
self::CIPHER,
|
|
self::key(),
|
|
OPENSSL_RAW_DATA,
|
|
$nonce,
|
|
$tag
|
|
);
|
|
|
|
if ($plaintext === false) {
|
|
throw new RuntimeException('Secret decryption failed.');
|
|
}
|
|
|
|
return $plaintext;
|
|
}
|
|
|
|
public static function mask(?string $value): string
|
|
{
|
|
$value = (string)$value;
|
|
if ($value === '') {
|
|
return '';
|
|
}
|
|
|
|
$length = strlen($value);
|
|
if ($length <= 4) {
|
|
return str_repeat('*', $length);
|
|
}
|
|
|
|
return substr($value, 0, 2) . str_repeat('*', max(4, $length - 4)) . substr($value, -2);
|
|
}
|
|
|
|
private static function key(): string
|
|
{
|
|
$keyMaterial = (string)($GLOBALS['ENCRYPTION_KEY'] ?? getenv('ENCRYPTION_KEY') ?: '');
|
|
if (trim($keyMaterial) === '') {
|
|
throw new RuntimeException('ENCRYPTION_KEY is required for replication secret encryption.');
|
|
}
|
|
|
|
return hash('sha256', $keyMaterial, true);
|
|
}
|
|
}
|