<?php

class EncryptorClass
{
    protected static $public_key;
    protected static $private_key;

    const KEYS_DIR = __DIR__ . '/dir/dir/dir/keys';
    const KEY_PASSPHRASE = 'bla-bla-bla';

    const MAX_DATA_LENGTH = 128;
    const DECRYPT_CHUNK_SIZE = 256;

    public static function encode($data)
    {
        if (is_numeric($data) === false && is_string($data) === false) {
            $data = json_encode($data);
        }

        $encrypted = null;

        // разбитие больших данных на куски
        if (strlen($data) > self::MAX_DATA_LENGTH) {
            $data = str_split($data, self::MAX_DATA_LENGTH);
        } else {
            $data = [$data];
        }
        $encrypt_result = true;
        foreach ($data as $chunk) {
            $encrypt_result = openssl_public_encrypt($chunk, $encrypted_chunk, self::$public_key);
            if ($encrypt_result === false) {
                break;
            }

            $encrypted .= $encrypted_chunk;
        }
        if ($encrypt_result) {
            $encrypted = base64_encode($encrypted);
        } else {
            $errors = [];
            while($error = openssl_error_string()) {
                $errors[] = $error;
            }

            $message = 'Не удалось зашифровать данные. ' . implode(" \n", $errors);
            error_log($message . "\n data: " . JSON::encode($data));
            throw new \Exception($message);
        }

        return $encrypted;
    }

    public static function decode(string $data, bool $jsonDecode = false)
    {
        /** @see https://s...content-available-to-author-only...p.net/manual/en/function.base64-decode.php#102113 */
        $encrypted = base64_decode(str_replace(' ', '+', $data));

        if ($encrypted === false) {
            $encrypted = $data;
        }

        $errors = [];
        $decrypted = null;

        // попытка разблокировать данные как просто зашифрованную строку
        if (openssl_private_decrypt($encrypted, $decrypted, openssl_pkey_get_private(self::$private_key, self::KEY_PASSPHRASE)) === false) {
            // разбитие для расшифровки длинных зашифрованных строк
            $decrypted = null;
            $encrypted = str_split($encrypted, self::DECRYPT_CHUNK_SIZE);
            foreach ($encrypted as $chunk) {
                $decrypt_result = '';
                if (openssl_private_decrypt($chunk, $decrypt_result, openssl_pkey_get_private(self::$private_key, self::KEY_PASSPHRASE))) {
                    $decrypted .= $decrypt_result;
                } else {
                    while($error = openssl_error_string()) {
                        $errors[] = $error;
                    }

                    $message = 'Не удалось расшифровать данные. ' . implode(" \n", $errors);
                    error_log($message . "\n data: " . JSON::encode($data));
                    throw new \Exception($message);
                }
            }
        }

        if ($jsonDecode && is_string($decrypted) && JSON::isValid($decrypted)) {
            $decrypted = JSON::decode($decrypted);
        }

        return $decrypted;
    }

    /**
     * Ключи RSA сгенерированы командами с заданием пароля self::KEY_PASSPHRASE:
     * openssl genrsa -des3 -out private_rsa.pem 2048
     * openssl rsa -in private_rsa.pem -outform PEM -pubout -out public_rsa.pem
     */
    public function __construct()
    {
        $public_key_path = realpath(self::KEYS_DIR . '/public_rsa.pem');
        $private_key_path = realpath(self::KEYS_DIR . '/private_rsa.pem');

        self::$public_key = file_get_contents($public_key_path);
        self::$private_key = file_get_contents($private_key_path);
    }
}