Мои мысли и проекты
Создаем сайт на Lumen (Laravel) - шифрование трафика
top crypto exchange software solutions for your business

Совсем немного теории

Без теории совсем никак, чтобы хотя бы себе объяснить что и как. И её реально будет немного. В основе шифрования лежат всего два алгоритма: RSA и AES.

  • RSA - ассиметричное шифрование. Т.е. шифруются данные ключом keyRSA1, а расшифровываются ключом keyRSA2 и keyRSA1 <> keyRSA2.
  • AES - симметричное шифрование. Т.е. данные и шифруются и расшифровываются одним и тем же ключом keyAES.

Алгоритм работы шифрования трафика

Сервер генерирует два ключа keyRSA1 (для шифрования данных, обычно называется публичным) и keyRSA2 (для расшифровки данных, обычно называется приватным) и отдает клиенту ключ для шифрования данных keyRSA1 (поэтому ключ и называется публичным что он доступен всем). Этот ключ у нас сохраняется в javascript который загружает себе пользователь при открытии страницы. После загрузке скрипта javascript происходит генерация случайного ключа keyAES и этот ключ не знает никто, кроме самого скрипта на машине пользователя и в идеальном виде этот ключ нигде не сохраняется. При отправке данных на сервер javascript шифрует их методом AES с помощью сгенерированного ключа keyAES. А также шифрует сам ключ методом RSA с помощью публичного ключа keyRSA1. После этого скрипт отправляет на сервер зашифрованный ключ и зашифрованные этим ключом данные. Если кто-то перехватит данные, то он не сможет расшифровать их, так как у него нет ключа. А ключ он не сможет расшифровать так как у него нет приватного ключа с сервера. Сервер получает данные. Расшифровывает ключ keyAES с помощью приватного ключа keyRSA2, а потом расшифровывает данные этим ключом данные. И все. Данные доставлены на сервер. При ответе сервер свой ответ шифрует данные ключом keyAES, который он получил с клиента. И в этом случае его не нужно отправлять обратно пользователю, так как он у пользователя уже имеется.

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

Список необходимых функций (на схеме эти блоки выделены зеленым). Префикс php - функции на PHP, префикс js - функции на JavaScript

  • phpGenerateRSAKeys() - генерация RSA ключей keyRSA1 и keyRSA2
  • phpDecodeRSA($data,$keyRSA2) - расшифровать данные ключом keyRSA2, закодированные методом RSA с помощью ключа keyRSA1
  • phpDecodeAES($data,$keyAES) - расшифровать данные ключом keyAES, закодированные методом AES с помощью ключа keyAES
  • phpEncodeAES($data,$keyAES) - зашифровать данные ключом keyAES
  • jsGenerateAESKeys() - генерация AES ключа keyAES
  • jsEncodeAES(data,keyAES) - зашифровать данные ключом keyAES
  • jsDecodeAES(data,keyAES) - расшифровать данные ключом keyAES, закодированные методом AES с помощью ключа keyAES
  • jsEncodeRSA(data,keyRSA1) - зашифровать данные ключом keyRSA1

Практическая реализация

Для реализации на javascript я пользовался следующим источником. Функция ShasoftCrypto на вход принимает следующие параметры

// Значения по умолчанию
var defaultOptions = {
    rsaName: "RSA-OAEP", // Имя RSA метода шифрования
    rsaKeyPublic: undefined, // Публичный ключ RSA
    rsaLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    aesName: "AES-CBC", // Имя AES метода шифрования
    aesLength: 256, // can be 128, 192, or 256
    ivLength: [16, 16], // Размер инициализирующего вектора для AES шифрования,
    hashName: "SHA-1"  // Имя хеширования
};

Метод ShasoftCrypto возвращает объект для работы с Shasoft Crypto Api

{
    rsa: {
        /**
          * Зашифровать данные методом RSA
          * 
          * @param {ArrayBuffer} abData - данные для шифрования
          * @param {string} [keyRSA1=options.rsaKeyPublic] Публичный (открытый) ключ RSA
          * @returns {Promise} Зашифрованные данные в формате ArrayBuffer
          */
        encode: function () { }
    },
    aes: {
        /**
          * Сгенерировать ключ AES
          * 
          * @returns {Promise} Ключ AES в формате string
          */
        generateKey: function () { },
        /**
          * Зашифровать данные методом AES
          * 
          * @param {ArrayBuffer} abData - данные для шифрования
          * @param {string} [keyAES=генерируется] Ключ AES
          * @returns {Promise} Зашифрованные данные в формате ArrayBuffer
          */
        encode: function () { },
        /**
          * Расшифровать данные методом AES
          * 
          * @param {ArrayBuffer} abData - данные для расшифровки
          * @param {string} [keyAES=генерируется] Ключ AES
          * @returns {Promise} Расшифрованные данные в формате ArrayBuffer
          */
        decode: function () { }
    },
    /**
      * Преобразовать Array Buffer в строку Base64
      * 
      * @param {ArrayBuffer} abData - данные в формате ArrayBuffer
      * @returns {string} строка в кодировке base64
      */
    abToB64: function () { },
    /**
      * Преобразовать строку base64 в Array Buffer
      * 
      * @param {string} str - строка в кодировке base64
      * @returns {ArrayBuffer} данные в формате ArrayBuffer
      */
    b64ToAb: function () { },
    /**
      * Преобразовать строку в Array Buffer
      * 
      * @param {string} str - строка
      * @returns {ArrayBuffer} данные в формате ArrayBuffer
      */
    strToAb: function () { },
    /**
      * Преобразовать Array Buffer в строку
      * 
      * @param {ArrayBuffer} abData - данные в формате ArrayBuffer
      * @returns {string} строка
      */
    abToStr: function () { },
    /**
      * Преобразовать JSON данные в Array Buffer
      * 
      * @param {any} value - данные в формате JSON
      * @returns {ArrayBuffer} данные в формате ArrayBuffer
      */
    jsonToAb: function () { },
    /**
      * Преобразовать Array Buffer в JSON
      * 
      * @param {ArrayBuffer} abData - данные в формате ArrayBuffer
      * @returns {any} данные в формате JSON
      */
    abToJson: function () { },
    /**
      * Вычислить ХЕШ строки
      * 
      * @param {ArrayBuffer} abData данные для вычисления хеш-а
      * @returns {string} хеш данных
      */
    hash: function () { }
};

Ключ AES представляет из себя строку в формате Base64.

Для реализации методов на PHP я использовал документацию. Класс PHP в конструктор принимает массив параметров. Параметры по умолчанию

[
    'rsaName' => "RSA-OAEP", // Имя RSA метода шифрования
    // Метод вызывается для получения публичного ключа RSA если он не указан при вызове метода
    'rsaKeyPublic' => function () {return null;},
    'rsaLength' => 2048,
    'aesName' => "AES-256-CBC", // Имя AES метода шифрования
    // Метод вызывается для получения ключа AES если он не указан при вызове метода
    'aesKey' => function () {return null;},
    'ivLength' => [16, 16], // Размер инициализирующего вектора для AES шифрования
    'hashName' => "SHA-1", // Имя хеширования
]

Сам класс содержит следующие методы

class Api
{
    // Параметры
    protected $options;
    // Используемое хранилище
    public function __construct($options = []);
    // Получить открытый(keyRSA1) и закрытий(keyRSA2) ключи для RSA шифрования
    public function generateRSAKeys();
    // Расшифровать данные ключом keyRSA2, закодированные методом RSA с помощью ключа keyRSA1
    public function decodeRSA($cryptData, $keyRSA2 = null);
    // Расшифровать данные AES
    public function decodeAES($data, $keyAES = null);
    // Зашифровать данные AES
    public function encodeAES($data, $aesKey);
    // Сгенерировать AES ключ
    public function generateAESKey();
}

Прозрачный обмен зашифрованными данными

Для удобства разработки лучше спрятать процесс шифрования/расшифровки обмена сообщениями клиента и сервера за функцией отправки данные на сервер. Алгоритм её работы весьма простой:

  • Шифруем данные и отправляем их на сервер, устанавливая заголовок S-Crypto = on
  • При получении данных сервер проверяет наличие заголовка. Если он указан, то данные расшифровываются закрытым ключом сервера + расшифрованным ключом клиента
  • После формирования сервером ответа происходит шифрование данных ключом клиента и устанавливается заголовок S-Crypto = on (теоретически ответ сервера может быть и не шифрованный)
  • При получения ответа клиент проверяет наличие заголовка и если он указан, то расшифровывает данные и возвращает клиенту

Для отправки данных пользуюсь библиотекой axios (документация на русском) (она является стандартом для использование с Vue хотя можно использовать и другую). Достаточно написать функцию отправки, основанную на axios. На серверной части Lumen в котором есть удобный механизм посредников. В этом посреднике я и буду расшифровывать данные, полученные от клиента и шифровать ответ от сервера (при необходимости).

Файлы проекта

При запуске файлов проекта необходимо открывать их по протоколу https. Если открывать по http, то функции Crypto Web API будут недоступны

0

Комментарии

Чтобы оставлять комментарии войдите на сайт. Вы можете сделать это через социальную сеть