10 KiB
Криптография
Перед данным протоколом стоит задача обеспечить гибкое шифрование с минимальным ущербом для совместимости между реализациями. Достигается это путём широкого спектра поддерживаемых "из коробки" криптографических алгоритмов и их комбинаций, вкупе с возможностью расширения этого списка в частных случаях, а также возможностью шифрования "в несколько слоёв". Реализуемая стадиумом криптографическая система, помимо базового свойства (сокрытие передаваемых данных от третьих лиц), обеспечивает следующие несколько вещей:
- Достоверность данных: передаваемые по протоколу данные подписываются с помощью ассиметричного шифрования, алгоритмом, выбранным на этапе подключения, что исключает возможность их подмены.
- Исключение атак повторного воспроизведения: каждый пакет снабжён уникальными идентификатором, что приводит к невозможности проведения replay-атак.
- Forward Secrecy: компрометация долговременного ключа или сессионных ключей не приводит к компрометации прошлых сессионных ключей.
- Future Secrecy: компрометация сессионных ключей не приводит к компрометации будущих сессионных ключей.
- Исключение различных вариаций статистического анализа: сессионные ключи регулярно проходят ротацию, в соединение подмешиваются "шумовые" пакеты, а в пакеты с реальными данными подмешиваются "шумовые данные", что размывает статистические характеристики и затрудняет анализ, предотвращая разные формы атак на основе шифротекста.
В рамках криптографической системы стадиума существуют несколько понятий, таких как:
- StreamId: идентификатор потока. В зашифрованном потоке уникален для каждого отдельного пакета, в незашифрованном - статичен на протяжении всей сессии. Используется для определения принадлежности пакета к потоку данных и, в контексте зашифрованного потока, исключения reply-атак. К одному потоку всегда привязаны ровно два идентификатора, один для использования клиентом и другой для сервера. В случае с зашифрованным потоком, обновляется с помощью KDF, в которой подаётся прошлое значение StreamId и константа.
- Packet Authentication Code: код аутентификации пакета. Используется для проверки целостности и достоверности пакета, создаётся путём вычисления хэша из тела события и последующей его подписи с помощью приватного SSK отправителя. Непрошедшие проверку целостности пакеты отбрасываются.
- Private Stream Signing Key: сессионный ключ для подписывания пакетов в шифрованном потоке. У сервера и клиента свои ключи.
- Public Stream Signing Key: сессионный ключ для верификации пакетов в шифрованном потоке. У сервера и клиента свои ключи.
- Packet Encryption Key: сессионный ключ для шифрования содержимого пакетов в шифрованном потоке с помощью симметричного алгоритма. У сервера и клиента свои ключи. В случае с зашифрованным потоком, обновляется с помощью KDF, в которой подаётся прошлое значение PEK и константа.
- Private Identity Key: долговременный приватный ключ. Хранится у сервера и используется для расшифровки полученных запросов рукопожатия.
- Public Identity Key: долговременный публичный ключ. Ассоциирован с сервером и используется для шифрования запроса рукопожатия при создании шифрованного потока.
Пример коммуникации двух узлов
Ниже приведена схема коммуникации двух узлов (Алиса - клиент, Боб - сервер), при условии, что PEK обновляются каждый пакет (т.е. уникальны для каждого отдельного пакета).
1: Alice -- request handshake --> Bob
2: Alice <-- accept handshake -- Bob
3: Alice -- send encrypted packet --> Bob
4: Alice and Bob updates Alice's PEK and StreamId using KDFs
5: Alice <-- send encrypted packet -- Bob
6: Bob and Alice updates Bob's PEK and StreamId using KDFs
7: Alice -- update PEK KDF const --> Bob
8: Alice and Bob updates Alice's PEK using KDF with new const and StreamId
9: Alice -- update StreamId KDF const --> Bob
10: Alice and Bob updates Alice's PEK and StreamId using KDF with new const
Более детальное описание схемы:
- Алиса отправляет Бобу зашифрованный с помощью публичного IK запрос рукопожатия с целью создания новой сессии и шифрованного потока, содержащий публичный SSK Алисы; инициализационное значение и константу для KDF, отвечающей за обновление PEK Алисы; инициализационное значение и константу для KDF, генерирующего StreamId Алисы; а также прочие параметры создаваемых сессии и потока.
- Боб принимает шифрованный запрос рукопожатия, расшифровывает с помощью своего приватного IK и отвечает Алисе своим публичным SSK; инициализационным значением и константой KDF, отвечающей за обновление PEK Боба; инициализационное значение и константу для KDF, генерирующего StreamId Боба; а также прочей своей частью параметров, зашифровав их с помощью публичного SSK Алисы.
- Алиса шифрует пакет с помощью своего PEK, подписывает с помощью своего приватного SSK, снабжает текущим StreamId и отправляет Бобу.
- Боб получает пакет, проверяет его с помощью публичного SSK Алисы и расшифровывает с помощью PEK Алисы. После этого Алиса и Боб обновляют PEK и StreamId Алисы с помощью двух KDF.
- Боб шифрует пакет с помощью своего PEK, подписывает с помощью своего приватного SSK, снабжает текущим StreamId и отправляет Алисе.
- Алиса получает пакет, проверяет его с помощью публичного SSK Боба и расшифровывает с помощью PEK Боба. После этого Боб и Алиса обновляют PEK и StreamId Боба с помощью двух KDF.
- Алиса запрашивает обновление константы KDF, генерирующей её PEK, помещая в пакет новое значение константы, после чего повторяя шаги из пункта 3.
- Боб получает пакет, проверяет его с помощью публичного SSK Алисы и расшифровывает с помощью PEK Алисы. Затем Алиса и Боб обновляют константу для KDF генерирующей PEK Алисы, а также сам PEK и StreamId Алисы с помощью двух KDF.
- Алиса запрашивает обновление константы KDF, генерирующей её StreamId, помещая в пакет новое значение константы, после чего повторяя шаги из пункта 3.
- Боб повторяет шаги из пункта 8, но вместо обновления константы KDF для генерации PEK - обновляет уже константу для генерации StreamId Алисы.