69 lines
2.2 KiB
PHP
Executable File
69 lines
2.2 KiB
PHP
Executable File
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App;
|
|
|
|
final class Crypto
|
|
{
|
|
private string $key;
|
|
|
|
public function __construct(Config $config)
|
|
{
|
|
if (!extension_loaded('sodium')) {
|
|
throw new \RuntimeException('libsodium extension not available');
|
|
}
|
|
|
|
$raw = getenv('DATA_KEY') ?: '';
|
|
$raw = trim($raw);
|
|
if ($raw === '') {
|
|
throw new \RuntimeException('DATA_KEY env not set');
|
|
}
|
|
|
|
// base64?
|
|
if (str_starts_with($raw, 'base64:')) {
|
|
$raw = substr($raw, 7);
|
|
}
|
|
$decoded = base64_decode($raw, true);
|
|
if ($decoded !== false && strlen($decoded) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
|
|
$raw = $decoded;
|
|
} elseif (ctype_xdigit($raw) && strlen($raw) >= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES * 2) {
|
|
$raw = hex2bin($raw);
|
|
}
|
|
|
|
if (strlen($raw) < SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
|
|
throw new \RuntimeException('DATA_KEY invalid length');
|
|
}
|
|
|
|
$this->key = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES);
|
|
}
|
|
|
|
public function encrypt(string $plaintext): string
|
|
{
|
|
if ($plaintext === '') {
|
|
return '';
|
|
}
|
|
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
|
$cipher = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($plaintext, '', $nonce, $this->key);
|
|
return base64_encode($nonce . $cipher);
|
|
}
|
|
|
|
public function decrypt(?string $blob): string
|
|
{
|
|
if ($blob === null || $blob === '') {
|
|
return '';
|
|
}
|
|
$raw = base64_decode($blob, true);
|
|
if ($raw === false || strlen($raw) <= SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
|
|
return '';
|
|
}
|
|
$nonce = substr($raw, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
|
$cipher = substr($raw, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
|
try {
|
|
$plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($cipher, '', $nonce, $this->key);
|
|
return $plain === false ? '' : $plain;
|
|
} catch (\Throwable) {
|
|
return '';
|
|
}
|
|
}
|
|
}
|