1, 'iv' => self::base64UrlEncode($iv), 'tag' => self::base64UrlEncode($tag), 'ct' => self::base64UrlEncode($ciphertext), ]); if (!is_string($payload)) { throw new \RuntimeException('Encryption payload encoding failed'); } return base64_encode($payload); } public static function decryptString(string $encoded): string { $key = self::getKey(); if ($key === null) { throw new \RuntimeException('Missing APP_CRYPTO_KEY'); } $json = base64_decode($encoded, true); if (!is_string($json) || $json === '') { throw new \RuntimeException('Encrypted payload is invalid'); } $payload = json_decode($json, true); if (!is_array($payload)) { throw new \RuntimeException('Encrypted payload cannot be decoded'); } $iv = self::base64UrlDecode((string) ($payload['iv'] ?? '')); $tag = self::base64UrlDecode((string) ($payload['tag'] ?? '')); $ciphertext = self::base64UrlDecode((string) ($payload['ct'] ?? '')); if ($iv === '' || strlen($iv) !== 12 || $tag === '' || strlen($tag) !== 16 || $ciphertext === '') { throw new \RuntimeException('Encrypted payload is malformed'); } $plaintext = openssl_decrypt( $ciphertext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag ); if (!is_string($plaintext)) { throw new \RuntimeException('Decryption failed'); } return $plaintext; } private static function getKey(): ?string { $raw = defined('APP_CRYPTO_KEY') ? trim((string) APP_CRYPTO_KEY) : ''; if ($raw === '') { return null; } // Accepts 64-char hex (32 bytes) or base64-encoded 32 bytes — both yield a 256-bit key. $hex = preg_match('/^[0-9a-f]{64}$/i', $raw) ? hex2bin($raw) : false; if (is_string($hex) && strlen($hex) === 32) { return $hex; } $base64 = base64_decode($raw, true); if (is_string($base64) && strlen($base64) === 32) { return $base64; } return null; } // URL-safe base64: replaces +/ with -_ and strips = padding. private static function base64UrlEncode(string $value): string { return rtrim(strtr(base64_encode($value), '+/', '-_'), '='); } private static function base64UrlDecode(string $value): string { $value = strtr($value, '-_', '+/'); $pad = strlen($value) % 4; if ($pad > 0) { $value .= str_repeat('=', 4 - $pad); } $decoded = base64_decode($value, true); return is_string($decoded) ? $decoded : ''; } }