Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Зашифрованное взаимодействие между заказчиком и сервером на Laravel 4

Anna | 31.05.2014 | нет комментариев

Каждому добросердечного времени суток! В своей первой статье по Laravel я хочу поделиться своим навыком организации зашифрованного взаимодействия между заказчиком (десктопным приложением) и сервером, работающем под управлением Laravel.

В чем суть задачи? Есть приложение, которое работает по тезису подписки: оплатил на сайте скажем месяц применения и месяц пользуешься. Как только данный срок прошел, приложение должно перестать трудиться. Так как по условию для работы этого приложения требуется подключение к интернету, то самым оптимальным вариантом проверять лицензионность приложения будет опрашивать сервер лицензий, тот, что по совместительству может быть еще и сайтом, через тот, что собственно и происходит оплата и продление периода применения приложения.

Но задача заключается в том, что если трафик между заказчиком и сервером не зашифрован, всякий желающий сумеет в результате подделать сервер и пользоваться приложением сколь желательно длинно.

В этой статье я расскажу о том, как организовать зашифрованное взаимодействие между заказчиком и сервером. В качестве серверной площадки будет приложение на движке Laravel 4.1 (последняя версия на момент публикации). Предполагается, что в качестве заказчика будет выступать приложение, написанное на C#, но в этой статье я не буду описывать написание заказчика. Взамен этого могу порекомендовать статью с CodeProject’а, в которой приводится пример применения криптографии на C#: Encrypting Communication between C# and PHP. Собственно эта статья и стала отправной точкой для моих исследований.

Я постараюсь осветить следующие вопросы:

  • основы криптографии (речь именно об основах, так как криптографией я не крепко обладаю);
  • программирование под Laravel, придерживаясь SOLID-дизайна (в идеале, но на практике, для облегчения статьи, не все SOLID-тезисы будут соблюдены)
  • Допустимо что-то еще

 

Основы криптографии

Все существующие алгорифмы шифрования дозволено поделить на 2 типа: синхронное и асинхронное шифрование. При синхронном шифровании применяется один ключ как для шифрования, так и напротив, для дешифрации. Алгорифмы этой категории значительно стремительней асинхронных.

В асинхронных алгорифмах применяются два ключа: открытый и закрытый. Шифрование производится с применением открытого ключа, при том расшифровка этих данных может быть осуществлена только с применением закрытого ключа. Помимо шифрования, есть еще такое представление, как цифровая подпись данных: хэш данных, сформированных с применением закрытого ключа. Такой хэш проверяется при помощи открытого ключа. Что это значит и для чего это необходимо? Это необходимо для того, Дабы убедиться, что полученные данные пришли именно от того, от кого мы ждем, и не были никем модифицированы на пути к нам. Асинхронные алгорифмы работают значительно неторопливей синхронных и предуготовлены огромнее для шифрования небольшого объема данных, но но надежнее. В силу этого, как правило, применяется дальнейшая схема взаимодействия между заказчиком и сервером:

  1. Сначала заказчик генерирует беспричинный ключ для синхронного шифрования.
  2. Дальше заказчик зашифровывает данный ключ посредством асинхронного алгорифма, применяя знаменитый ему предварительно публичный ключ сервера.
  3. Зашифрованный ключ отправляется серверу.
  4. Сервер получает и расшифровывает полученный ключ и результатом информирует заказчику, что все отлично.
  5. Последующий трафик между сервером и заказчиком шифруется посредством выбранного синхронного алгорифма.

Для чего это необходимо? Для того, Дабы недопустить атаку типа man in the middle (человек посередине), когда трафик перехватывается и применяется преступником в своих целях.

В нашем же случае помимо шифрации данные будут еще подписываться, Дабы заказчик был на 100% уверен, что данные получил именно от своего сервера, а не от допустимо подделанного.

Немножко о структуре серверного приложения

Я не буду тут рассказывать, как сделать пустое Laravel приложение, об этом дозволено прочитать наофициальном сайте фреймворка. Представим Вы теснее сотворили приложение с именем MySecureApp. Каждый наш код будет размещен внутри директории app. А вернее, помимо контроллеров, моделей и вьюх, сделаем вот еще что:

  1. Внутри папки app сделайте папку lib, в ней MySecureApp — тут будут располагаться все наши классы, реализующие бизнес логику приложения
  2. Внутри папки app сделайте папку keys. В ней будет храниться наш закрытый ключ.
  3. Отредактируйте файл composer.json в корне приложения, добавив следующие строки:
    "autoload": {
      "classmap": [
        "app/commands",
        "app/controllers",
        "app/models",
        "app/database/migrations",
        "app/database/seeds",
        "app/tests/TestCase.php"
      ],
      "psr-0": {
        "MySecureApp": "app/lib"
      }
    },

    Позже чего нужно исполнить команду, Дабы авто-загрузчик видел наши классы:

    composer dump-autoload
  4. У самого нашего приложения должна быть дальнейшая конструкция директорий:
    /app
      /lib
        /MySecureApp
          /Cryptography - классы, непринужденно осуществляющие шифрование
          /Dto          - Data Transfer Objects
            /Responses  - классы-результаты нашего API
          /Facades      - Фасады для комфортного доступа к некоторым классам
          /Filters      - фильтры
          /Helpers      - классы-помощники
          /Providers    - Service providers, регистрирующие наш функционал в приложении Laravel

    Понемногу мы наполним эти директории классами.

 Клиент-серверное взаимодействие

Все взаимодействие между клиентским приложением и сервером будет протекать через исключительный контроллер — ApiController. То есть через Url’ы вида mysecureapp/api/*

Логика дальнейшая: заказчик отправляет POST запрос на волнующий его способ api, передавая всякий параметр в зашифрованном виде. В результат сервер возвращает JSON-результат вида:

{
    "data": "<AES encrypted JSON object>",
    "sign": "<RSA signature of data>"
}

Реализация криптографии

В качестве симметричного алгорифма шифрования будем применять AES. В качестве ассиметричного — RSA. К счастью, совместно с Laravel’ом теснее поставляется криптографическая библиотека phpseclib, которая содержит все нам нужное. Начнем с RSA.

Для работы RSA нам потребуются пара ключей — открытый и закрытый. Ну вернее если говорить о серверной реализации, то потребуется только закрытый ключ. Открытый ключ будет необходим заказчику. Давайте сгенерируем эту пару ключей.

Для этого нам потребуется установленный на компьютере OpenSSL. Насколько мне вестимо, на Linux системах он установлен по умолчанию. Для Windows его дозволено скачать отсель: http://slproweb.com/products/Win32OpenSSL.html. Лично у меня появились сложности с применением Light дистрибутивов — в них не было нужного для работы openssl.cfg. Следственно желанно скачать и установить полную версию (~19 Мб). Позже установки нужно сделать переменную окружения OPENSSL_CONF, указывающую на вышеупомянутый конфиг. Сделать это дозволено в консоли, набрав

set OPENSSL_CONF = \путь\к\openssl.cfg

Приступим к созданию ключей. Запустите командную строку и перейдите (cd) в директорию, куда только что установили openssl, а вернее во вложенную директорию bin. Для генерации закрытого ключа, исполните ступенчато следующие две команды:

openssl genrsa -aes256 -out temp.key 1024
openssl rsa -in temp.key -out private.key

Сейчас на основе полученного ключа сгенерируем X509-сертификат, либо иными словами — открытый ключ:

openssl req -new -x509 -nodes -sha1 -key private.key -out public.crt -days 365000

Вам будут заданы несколько вопросов, отвечать на которые не непременно. Дозволено ответить что желательно.
Итого имеем:

  1. private.key — закрытый ключ
  2. public.crt — открытый

Перенесите их в подготовленную теснее для этого папку app/keys, а в конфиг приложения (app/config/app.php) добавьте следующую строчку:

'privateKey' => 'private.key',

Раньше чем приступать к реализации RSA, сделаем вспомогательный класс для кодирования/раскодирования строк в/из Base64. Сделайте файл app/lib/MySecureApp/Helpers/Base64.php со дальнейшим содержимым:

<?php namespace MySecureApp\Helpers;

class Base64 {
    public static function UrlDecode($x)
    {
        return base64_decode(str_replace(array('_','-'), array('/',' '), $x));
    }

    public static function UrlEncode($x)
    {
        return str_replace(array('/',' '), array('_','-'), base64_encode($x));
    }
}

Ну а сейчас приступим к непринужденно реализации RSA. Для этого сотворим класс Cryptography вapp/lib/MySecureApp/Cryptography/Cryptography.php:

<?php namespace MySecureApp\Cryptography;

use MySecureApp\Helpers\Base64;

class Cryptography {
    /**
     * RSA instance
     * @var \Crypt_RSA
     */
    protected $rsa;

    /**
     * RSA private key
     * @var string
     */
    protected $rsaPrivateKey;

    /**
     * Whether RSA instance is initialized
     * @var bool
     */
    private $isRsaInitialized = false;

    /**
     * Initializes the RSA instance using either provided private key file or config value
     * @param String $privateKeyFile Path to private key file
     * @throws Exception
     */
    public function initRsa($privateKeyFile = '') {
        //
    }

    /**
     * Decrypts RSA-encrypted data
     * @param String $data Data to decrypt
     * @return String
     */
    public function rsaDecrypt($data) {
        //
    }

    /**
     * Encrypts data using RSA
     * @param String $data Data to encrypt
     * @return String
     */
    public function rsaEncrypt($data) {
        //
    }

    /**
     * Signs provided data
     * @param String $data Data to sign
     * @throws \Exception
     * @return string Signed data
     */
    public function rsaSign($data) {
        //
    }
}

 

Малое примечание: предлагаемый вариант класса Cryptography не вовсе соответствует тезисам SOLID дизайна. Я теперь делаю это специально с целью упростить материал. О том, как его дозволено усовершенствовать я расскажу в конце статьи.

Приступим к наполнению RSA способов. Начнем с rsaInit(). Алгорифм примитивный: считываем закрытый ключ, переданный нам в параметре, либо взятый из конфига, и инициализируем класс Crypt_RSA, поставляемый библиотекой phpseclib:

    public function initRsa($privateKeyFile = '') {
        // Если не указан параметр, берем ключ из конфига
        if (!$privateKeyFile) {
            $privateKeyFile = app_path() . '/keys/' . \Config::get('app.privateKey');
        }

        // Проверяем, существует ли такой файл
        if (!\File::exists($privateKeyFile)) {
            Log::error("Error reading private key file.");
            throw new Exception("Error reading private key file.");
        }

        $this->rsaPrivateKey = \File::get($privateKeyFile);

        // Сама инициализация RSA
        $rsa = new \Crypt_RSA();
        $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
        $rsa->loadKey($this->rsaPrivateKey);

        // Запоминаем экземпляр в приватной переменной и 
        // устанавливаем флаг в true во избежание лишних
        // (повторных) инициализаций
        $this->rsa = $rsa;
        $this->isRsaInitialized = true;
    }

Сейчас реализуем сами способы для работы сданными:

    public function rsaDecrypt($data) {
        // инициализируем RSA если это еще не было сделано
        if (!$this->isRsaInitialized) {
            $this->initRsa();
        }

        // сама дешифрация
        return $this->rsa->decrypt(Base64::UrlDecode($data));
    }

    // ...

    public function rsaEncrypt($data) {
        // подобно rsaDecrypt
        if (!$this->isRsaInitialized) {
            $this->initRsa();
        }

        return Base64::UrlEncode($this->rsa->encrypt($data));
    }

    // ...

    public function rsaSign($data) {
        if (!$this->isRsaInitialized) {
            $this->initRsa();
        }

        // проверяем, установлено ли PHP-растяжение openssl
        if (!function_exists('openssl_sign')) {
            throw new \Exception("OpenSSL is not enabled.");
        }

        // формируем подпись
        $signature = '';
        $keyId = openssl_get_privatekey($this->rsaPrivateKey);
        openssl_sign($data, $signature, $keyId);
        openssl_free_key($keyId);

        return $signature;
    }

Обратите внимание, что способ rsaDecrypt ждет, что переданные данные закодированы в Base64. Симметрично, rsaEncrypt возвращает зашифрованные данные, закодированные в Base64.

На этом RSA часть класса Cryptography закончена. Приступим к AES.

Добавляем поля к классу:

    /**
     * AES instance
     * @var \Crypt_AES
     */
    protected $aes;

    /**
     * Whether AES instance is initialized
     * @var bool
     */
    private $isAesInitialized = false;

Сейчас способы:

    /**
     * Initializes AES instance using either provided $options or session values
     * @param array $options Array of options, containing 'key' and 'iv' values
     * @throws Exception
     */
    public function initAes($options = array()) {
        // ...
    }

    /**
     * Encrypts data using AES
     * @param String $data Data to encrypt
     * @return String
     */
    public function aesEncrypt($data) {
        // ...
    }

    /**
     * Decrypts AES encrypted data
     * @param String $data Data to decrypt
     * @return String
     */
    public function aesDecrypt($data) {
        // ...
    }

Инициализация AES:

    public function initAes($options = array()) {
        // Если $options пустой, то берем ключ из сессии
        if (empty($options) && Session::has('aes_key') && Session::has('aes_iv')) {
            $options = array(
                'key'   => Session::get('aes_key'),
                'iv'    => Session::get('aes_iv'),
            );
        }

        // Если и в сессии ключа не оказалось, то выбрасываем исключение
        if (!(isset($options['key']) && isset($options['iv']))) {
            Log::error("Either key or iv not set");
            throw new Exception("Either key or iv not set");
        }

        // Запоминаем ключ в сессии
        Session::put('aes_key', $options['key']);
        Session::put('aes_iv', $options['iv']);

        // Инициализируем Crypt_AES, поставляемый библиотекой phpseclib
        $aes = new \Crypt_AES(CRYPT_AES_MODE_CBC);
        $aes->setKeyLength(256);
        $aes->setKey(Base64::UrlDecode($options['key']));
        $aes->setIV(Base64::UrlDecode($options['iv']));
        $aes->enablePadding();

        // Запоминаем и устанавливаем флаг
        $this->aes = $aes;
        $this->isAesInitialized = true;
    }

Сейчас сами способы обработки данных:

    public function aesEncrypt($data) {
        // Все по аналогии с RSA
        if (!$this->isAesInitialized) {
            $this->initAes();
        }

        return $this->aes->encrypt($data);
    }

    public function aesDecrypt($data) {
        if (!$this->isAesInitialized) {
            $this->initAes();
        }

        return $this->aes->decrypt($data);
    }

На этом класс Cryptography готов. Но пока это лишь инструмент, а как им пользоваться, хоть как бы и видимо, будет показано дальше.

Дальше нам необходим инструмент, тот, что бы выдавал расшифрованные входящие данные. Что я имею ввиду? К примеру, заказчик хочет авторизоваться, и для этого он (я немножко забегаю вперед), отправляет POST запрос на mysecureapp/api/login со следующими данными: email=asdpofih345kjafg и password=zxcvzxcvzxcvzxcv — это зашифрованные AES’ом данные. Для приобретения расшифрованных данных нам потребуется класс, подобный фасаду Input, но возвращающий теснее расшифрованные данные. Назовем его DecryptedInput и сотворим его в app/lib/MySecureApp/Cryptography/DecryptedInput.php. Реализуем в нем особенно знаменитые способы Input’а: get(), all() и only():

<?php namespace MySecureApp\Cryptography;

use MySecureApp\Helpers\Base64;

/**
 * Provides funcitonality for getting decrypted Input paramters
 * (encrypted with AES)
 * Class DecryptedInput
 * @package MhBot\Cryptography
 */
class DecryptedInput {

    /**
     * Array of raw (non-decrypted) input parameters
     * @var array
     */
    protected $params;

    /**
     * Array of decrypted values
     * @var array
     */
    protected $decryptedParams = array();

    /**
     * @var Cryptography
     */
    protected $crypt;

    /**
     * @param Cryptography $crypt Injected Cryptography object used for decrypting
     */
    public function __construct(Cryptography $crypt) {
        // Получаем в конструкторе экземпляр Cryptography
        // инъекцией
        $this->crypt = $crypt;

        // Запоминаем все данные в $params
        $this->params = \Input::all();
    }

    /**
     * Returns decrypted input parameter
     * @param $key
     * @return String
     */
    public function get($key) {
        // Проверяем, не был ли теснее гетнут данный параметр 
        if (isset($this->decryptedParams[$key])) {
            return $this->decryptedParams[$key];
        }

        // расшифровываем
        $value = $this->crypt->aesDecrypt(Base64::UrlDecode($this->params[$key]));
        // сберегаем расшифрованную версию параметра
        $this->decryptedParams[$key] = $value;

        // и собственно возвращаем
        return $value;
    }

    /**
     * Returns all input params decrypted
     * @return array
     */
    public function all() {
        // обходим все параметры и расшифровываем их
        foreach ($this->params as $key => $value) {
            $this->decryptedParams[$key] = $this->get($key);
        }

        // возвращаем полный массив расшифрованных данных
        return $this->decryptedParams;
    }

    /**
     * Returns only specified input parameters
     * @return array
     */
    public function only() {
        $args = func_get_args();
        $result = array();
        foreach($args as $arg) {
            $result[$arg] = $this->get($arg);
        }

        return $result;
    }

}

Обратите внимание на строку 33: в конструктор передается экземпляр Cryptography. Но согласитесь, неудобно было бы нам непрерывно «вручную» инициализировать данный класс, следственно мы поступим в наилучших традициях Laravel — Дабы он сделал все за нас.

Для этого мы проделаем следующее:

  1. Сделаем из DecryptedIntput фасад, верно такой же, как и обыкновенный Input
  2. Сделаем из Cryptography синглтон.
  3. «Зарегистрируем» все это дело внутри нашего собственного Service Provider‘а.

Выходит, идем по порядку. Делаем фасад DecryptedInput. Для этого создаем файл DecryptedInput.php вapp/lib/MySecureApp/Facades:

<?php namespace MySecureApp\Facades;

use Illuminate\Support\Facades\Facade;

class DecryptedInput extends Facade {

    protected static function getFacadeAccessor()
    {
        // "ключ доступа", по которому будет извлекаться
        // DecryptedInput из контейнера
        return 'decryptedinput';
    }

}

Допустимо у Вас возникнет путаница в именах: у нас есть два класса с именем DecryptedInput: один — аналог Input’а, иной — его фасад, легко у них различные пространства имен. Следственно вероятно фасад логичнее было бы переименовать в DecryptedInputFacade. Но это только информация на заметку — решать вам. Вследствие пространствам имен мы неизменно можем верно указать, какой класс собираемся применять.

Сейчас у нас все готово к тому, Дабы написать личный Service Provider (пишу это по-английски, так как не придумал пока достойного перевода этого термина, буквально это будет подрядчик служб, но сервис провайдер мне огромнее нравится). Сделаем файл CryptoServiceProvider.php в app/lib/MySecureApp/Providersсо дальнейшим содержимым:

<?php namespace MySecureApp\Providers;

use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use MySecureApp\Cryptography\Cryptography;
use MySecureApp\Cryptography\DecryptedInput;

class CryptoServiceProvider extends ServiceProvider {

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        // Регистрируем синглтон Cryptograpgy
        $this->app->singleton('cryptography', function() {
            return new Cryptography();
        });

        // Регистрируем наш аналог Input'а по ключу 'decryptedinput'
        $this->app['decryptedinput'] = $this->app->share(function($app) {
            return new DecryptedInput($app['cryptography']);
        });

        // Регистрируем алиас на фасад DirectInput'а
        $this->app->booting(function() {
            $loader = AliasLoader::getInstance();
            $loader->alias('DecryptedInput', 'MySecureApp\Facades\DecryptedInput');
        });
    }
}

Ну что, могу поздравить нас с тем, что половину работы мы проделали. Осталось еще столько же. Шучу, чуть меньше… В реальности к данному моменту мы лишь подготовили арсенал инструментов, которые нам еще предстоит применять.

Дозволено передохнуть. А пока я лаконично расскажу, как это работает. Начну с DecryptedInput‘а. Сейчас мы можем применять его так:

    // ...
    $email = DecryptedInput::get('email');
    $password = DecryptedInput::get('password');

    // либо ...
    extract(DecryptedInput::only('email', 'password'));
    // при этом создадутся 2 локальные переменные:
    // $email и $password

Как это происходит? Вследствие алиасу, зарегистрированному в провайдере, Laravel знает, что при обращении к классу DecryptedInput необходимо применять сделанный нами фасад. А как работает фасад? Вследствие возвращаемому способом getFacadeAccessor() ключу (аксессору) ‘decryptedinput‘, Laravel знает, что при всех обращениях к статическим способам фасада необходимо «дергать» способы класса\MySecureApp\Cryptography\DecryptedInput (ключ ‘decryptedinput’ зарегистрирован провайдером):

        // ...
        $this->app['decryptedinput'] = $this->app->share(function($app) {
            // Тут под DecryptedInput подразумевается именно аналог
            // Input'а, от того что а) фасад еще не зарегистрирован,
            // и б) в начала провайдера мы прописали
            // use MySecureApp/Cryptography/DecryptedInput
            // Обратите также внимание, что в конструктор передается
            // синглтон Cryptography, зарегистрированный выше
            return new DecryptedInput($app['cryptography']);
        });
        // ...

Ну что, можем продолжать :) Как я теснее упоминал, всё взаимодействие заказчика с сервером происходит через ApiController. В чем специфика этого контроллера?

  1. Ему передаются зашифрованные данные
  2. Он должен возвращать зашифрованный результат.

Как поступать с получаемыми зашифрованными данными мы теснее разобрались — для этого естьDecryptedInput. А как возвращать зашифрованный результат? Да еще и подписанный? Допустимо кому придет в голову в всяком способе контроллера шифровать данные. Но это не успешный подход. Во-первых, необходимо во всех способах соблюдать один и тот же формат данных. Во-вторых, это непотребный копи-паст в всяком способе ( я имею ввиду шифрование данных). Здесь нам на поддержка придет восхитительная фишка Laravel — фильтры. А именно, нам необходим каждого лишь один after фильтр, тот, что будет шифровать и форматировать все исходящие данные. Таким образом, все способы апи-контроллера будут легко возвращать сами данные в чистом (plain) виде, а after-фильтр их теснее будет шифровать и подписывать.

Что же, давайте  напишем данный фильтр. Сделайте файл OutgoingCryptFilter.php вapp/lib/MySecureApp/Filters со дальнейшим содержимым:

<?php namespace MySecureApp\Filters;

use MySecureApp\Cryptography\Cryptography;
use MySecureApp\Helpers\Base64;

/**
 * Class OutgoingCryptFilter
 * Encrypts and signs the response
 *
 * @package MySecureApp\Filter
 */
class OutgoingCryptFilter {

    private $crypt;

    public function __construct(Cryptography $crypt) {
        // Данный фильтр будет резолвится из контейнера,
        // следственно нам не необходимо заботится о передаче
        // объекта Cryptography - Laravel сделает это независимо
        $this->crypt = $crypt;
    }

    // Сам фильтр
    public function filter($route, $request, $response) {
        // Вначале получим начальные данные, которые вернул
        // способ контроллера
        $content = $response->getOriginalContent();
        if (!is_string($content)) {
            $content = json_encode($content);
        }

        // Зашифруем их
        $content = Base64::UrlEncode($this->crypt->aesEncrypt($content));
        // И сформируем подпись
        $sign = Base64::UrlEncode($this->crypt->rsaSign($content));

        // Вернем стандартизованный объект (ну вернее массив) вида:
        //     'data' => $content, - данные
        //     'sign' => $sign,    - подпись
        // "Вернем" не вовсе правильно, мы перезаписываем данные результата
        $response->setContent(['data' => $content, 'sign' => $sign]);
    }
}

Как видите, фильтр достаточно примитивен. Хочу еще раз обратить внимание на конструктор: он будет вызван при создании фильтра Laravel’ом, а он довольно разумен, Дабы распознать параметры конструктора и подставить требуемый объект.

Сейчас нужно зарегистрировать данный фильтр. Тут мудрствовать не будем и воспользуемся предусмотренным для этого местом: app/filters.php:

// Назовем фильтр cryptOut
Route::filter('cryptOut', 'MySecureApp\Filters\OutgoingCryptFilter');

Осталось вовсе чуть-чуть. Написать сам контроллер с как минимум двумя способами: инициализация соединения между заказчиком и сервером (так называемый хэндшейк) и какой-нить демонстрационный способ, возвращающий данные.

Воспользуюсь примером из собственной жизни. Цель — авторизовать клиентское приложение и воротить ему информацию о том, разрешено ли ему выполняться (что-то как бы проверки лицензии).

Выходит, вот конструкция контроллера ApiController:

<?php

use MySecureApp\Cryptography\Cryptography;

class ApiController extends BaseController {

    /**
     * @var Crypt
     */
    private $crypt;

    public function __construct(Cryptography $crypt) {
        $this->crypt = $crypt;

        // добавляем after-фильтр (либо если хотите исходящий фильтр)
        // обратите внимание на 2-й довод: массив с полем except:
        // мы не хотим шифровать результат способа postInit, от того что на этом
        // этапе наша криптография еще не готова
        $this->afterFilter('cryptOut', array('except'   => 'postInit'));
    }

    // Инициализация (хэндшейк) между заказчиком и сервером
    // Это 1-й этап: заказчик передает нам RSA-зашифрованный
    // ключ к AES шифратору. Ключ состоит из 2-х переменных: key и iv
    public function postInit() {
        // Проверяем, пришел ли ключ
        if (!(Input::has('key') && Input::has('iv'))) {
            return 'ERROR 1';
        }

        // Извлекаем ключ в переменные $key и $iv
        extract(Input::only('key', 'iv'));
        // Расшифровываем их
        $key = $this->crypt->rsaDecrypt($key);
        $iv = $this->crypt->rsaDecrypt($iv);

        // Если правда бы один из них == false (не удалось расшифровать)
        // вернем заказчику ошибку
        if (!($key && $iv)) {
            return 'ERROR 2';
        }

        // Инициализируем AES полученным ключом
        $this->crypt->initAes(array(
            'key'   => $key,
            'iv'    => $iv,
        ));

        return 'OK';
    }
}

Это была инициализация взаимодействия заказчика с сервером. В случае триумфа, заказчику вернется легко текстовое сообщение OK. В случае ошибки, ERROR. Я осмысленно не стал возвращать текстовое сообщение об ошибке — потенциальному взломщику не необходимо знать, что здесь твориться.

Сейчас давайте напишем какой-нибудь способ, тот, что теснее требовал бы шифрования. Предлагаю написать авторизацию. Заказчик передает нам свой email и password, а в результат сервер возвращает данные: удачно ли авторизовался заказчик либо нет, и когда истекает его лицензия. Я ограничусь лишь контроллером и Dto-объектом LoginResponse. Модель всякий может написать сам, для демонстрации это будет лишним.

Для начала сотворим базовый класс для всех результатов сервера.app/lib/MySecureApp/Dto/Responses/ResponseBase.php:

<?php namespace MySecureApp\Dto\Responses;

abstract class ResponseBase implements \JsonSerializable {
    // тип результата
    public $type;
}

Проще некуда. У класса одно только поле — это тип результата. Вследствие этому полю заказчик сумеет написать что-то как бы диспетчера пакетов. Сейчас напишем определенный вариант результата:LoginResponse (app/lib/MySecureApp/Dto/Responses/LoginResponse.php):

<?php namespace MySecureApp\Dto\Responses;

class LoginResponse extends ResponseBase {

    const LOGIN_SUCCESS = true;
    const LOGIN_FAIL = false;

    public $loginResult; // Итог авторизации
    public $expire; // Дата, когда истекает срок действия лицензии

    public function __construct() {
        $this->type = 'login';
        $this->expire = '0000-00-00 00:00:00';
    }

    /**
     * (PHP 5 &gt;= 5.4.0)<br/>
     * Specify data which should be serialized to JSON
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by <b>json_encode</b>,
     * which is a value of any type other than a resource.
     */
    public function jsonSerialize()
    {
        return [
            'type'          => $this->type,
            'loginResult'   => $this->loginResult,
            'expire'        => $this->expire,
        ];
    }
}

Сейчас сам способ контроллера postLogin:

    public function postLogin() {
        // получаем расшифрованные креденшелы
        $creds = [
            'email' => DecryptedInput::get('email'),
            'password'  => DecryptedInput::get('password'),
        ];

        $response = new \MySecureApp\Dto\Responses\LoginResponse;

        if (!Auth::attempt($creds, false)) {
            // Если авторизоваться не удалось, выставляем соответствующее значение в loginResult
            $response->loginResult = \MySecureApp\Dto\Responses\LoginResponse::LOGIN_FAIL;
            // и ретурним
            return json_encode($response);
        }
        $response->loginResult = \MySecureApp\Dto\Responses\LoginResponse::LOGIN_SUCCESS;

        $response->expire = Auth::user()->tariffExpire;
        return json_encode($response);
    }

Ну вот собственно и все. Обратите внимание на 4 и 5 строки — мы используем DecryptedInput для приобретения переданных нам в POSTе данных.

Regards,
Александр [Амега] Егоров.

P.S. Чуть не позабыл, я же обещал рассказать, как дозволено изменить Cryptography для большей эластичности. Задача данного кода в том, что он всецело завязан на связке RSA AES, причем проявляется это даже в именах способов (aesEncrypt, rsaSign и т.д.) А это не есть отлично. Каждое может быть — внезапно придется отказаться от этих 2-х алгорифмов и испол

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB