= 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 ''; } } }