Памятка по созданию безопасного канала связи

1 сентября 2010

Данный пост представляет собой что-то вроде конспекта, составленного по книге Брюса Шнайера и Нильса Фергюсона «Практическая криптография». Книга эта невероятно интересная! Если вы интересуетесь криптографией, то просто обязаны держать ее на полке (в крайнем случае — скачать электронную версию), ровно как и любую другую книгу этих авторов, которую сможете найти.

Важно! Если перед вами стоит практическая задача, советую найти настоящего специалиста по криптографии, а не полагаться на бложик никому неизвестного программиста. Учтите также, что книга была написана в 2005 году и к тому моменту, когда вы найдете эту заметку, информация может устареть.

Практическая криптографияЕсть несколько причин, по которой я взялся за написание этой памятки. Для начала, я хочу иметь под рукой краткую заметку по созданию безопасного соединения. Есть подозрения, что когда-нибудь она мне пригодится. Объем «Практической криптографии» — более 400 страниц, так что на краткое руководство оно не тянет. Тем не менее, ознакомиться с книгой стоит для понимания того, почему авторы предлагают то решение, а не иное. Другая причина создания этого поста — мой экземпляр книги начинает потихоньку разваливаться. Третья — информация в интернете доступна отовсюду, а не только у меня дома.

Объяснение таких понятий, как блочный шифр, функция хэширования, цифровая подпись и тд в этом тексте вы не найдете. Обращайтесь к соответствующей литературе или Википедии. Если вы хотели бы увидеть в этом блоге посты из серии «основы криптографии», отпишитесь в комментариях — посмотрим, что тут можно сделать.

1. Постановка задачи

Есть пользователи А и Б, которые обмениваются сообщениями. Они могут использовать протокол TCP, UDP, электронную почту или чат — сейчас это не имеет значения. Есть злоумышленник Е, который может перехватывать, подменять и переставлять эти сообщения, заставлять их бесследно исчезнуть, а также слать сообщения от имени А и Б. Задача — помочь А и Б обмениваться сообщениями таким образом, чтобы Е не мог узнать их содержимого. Избавиться от Е никак нельзя.

2. Шифрование данных

В качестве алгоритма шифрования Шнайер и Фергюсон рекомендуют использовать AES (Rijndael с размером блока 128 бит) с длиной ключа 256 бит в режиме CTR. В этом режиме максимальный размер одного сообщения составляет 16 · 232 байт. Вряд ли кого-то стеснит это ограничение. В крайнем случае можно разбить сообщение на части.

Для шифрования сообщения с номером i, имеющего размер L бит, необходимо вычислить L бит следующим способом:

b1, b2, …, bL = EK(0 [32 бита]|| i [32 бита]|| 0 [64 бита]) || EK(1 || i || 0 ) ||
EK(2 || i || 0) || EK(3 || i || 0) || …

где функция EK (128 бит на выходе) — алгоритм шифрования, K (256 бит) — ключ шифрования, аргумент функции (128 бит) — шифруемые данные, а оператор || означает конкатенацию данных. После вычисления битов b1 … bL складываем их по Жегалкину (исключающее ИЛИ, операция XOR, ⊕) с битами сообщения, в результате получаем зашифрованный текст. Для расшифровки нужно проделать точно такую же последовательность действий над шифротекстом.

Здесь важно обратить внимание на два момента. Во-первых, ключи шифрования при посылке данных от А к Б и от Б к А должны быть разными. Во-вторых, необходимо согласовать последовательность байт в аргументе функции EK. Порядок байт в 32-х разрядных числах может меняться в зависимости от архитектуры используемого процессора. Традиционно в сетевых протоколах и форматах файлов используется порядок байт от старшего к младшему.

3. Функция хэширования

В качестве функции хэширования авторы рекомендуют SHA-256. Она используется при вычислении кодов аутентификации сообщений, а также при обмене ключами. В последнем случае применяется функция SHAd-256, определенная, как SHA256(SHA256(message)).

Допустим, мы успешно установили сеансовый ключ с помощью алгоритма Диффи-Хеллмана, в результате А и Б получили общий ключ К. Затем производится следующая последовательность действий:

// Избавляемся от алгебраической структуры ключа
K = SHAd-256(K)
// Строим 4 дочерних ключа:
// 1. Шифрование от А к Б
key_send_enc = SHAd-256(K || «Enc from A to B»)
// 2. Шифрование от Б к А
key_recv_enc = SHAd-256(K || «Enc from B to A»)
// 3. Аутентификация от А к Б
key_send_auth = SHAd-256(K || «Auth from A to B»)
// 4. Аутентификация от Б к А
key_recv_auth = SHAd-256(K || «Auth from B to A»)

4. Коды аутентификации сообщений

Каждое сообщение должно сопровождаться имитовставкой (MAC). В качестве функции MAC Шнайер и Фергюсон рекомендуют использовать HMAC-SHA-256:

ai = HMACK(i || L(xi) || xi || mi)
HMACK(m) = SHA256{(K ⊕ 0x5c5c..5c) || SHA256[(K ⊕ 0x3636..36) || m]}

Здесь ai — код аутентификации i-го сообщения mi. HMACK(m) представляет собой хэш-функцию, значение которой зависит не только от сообщения m, но и от ключа K. Как и в случае с шифрованием, в MAC должно использоваться два независимых ключа для А и Б. Как уже отмечалось, ⊕ — это операция побитового исключающего ИЛИ (она же XOR).

В «Практической криптографии» делается особый акцент на том, как важно производить аутентификацию не только сообщения mi, являющегося обычной последовательностью байт, но и его смысла. Пусть mi = a || b || c, то есть состоит из нескольких полей определенной длины. Представим, что после очередного обновления протокола размеры полей изменилось. Тогда, если злоумышленник Е сможет подменить версию протокола, сообщение будет интерпретировано неверно. Притом неважно как злоумышленнику удастся это сделать — безопасность одной части системы не должна зависеть от безопасности остальных.

Вот для чего нужна дополнительная информация xi, включающая в себя id протокола (сигнатуру, IP:порт получателя и отправителя), версию протокола, id сообщения, размер и имена полей сообщения. Притом информация xi должна быть закодирована таким образом, чтобы ее можно было однозначно декодировать. L(xi) в нашей формуле — это, очевидно, длина xi. К счастью, всего этого гемора можно избежать, просто передавая сообщения в XML-подобном формате данных, то есть таком, где дополнительная информация о смысле сообщения уже включена в само сообщение. Тут важно не забыть включить в каждое сообщение всю дополнительную информацию, перечисленную выше.

Что еще следует знать о MAC?

  • Можно обрезать значение HMAC-SHA-256 до 16-и байт. Делать это не желательно, но такое решение лучше, чем использование HMAC-MD5 с целью уменьшить объем передаваемых данных.
  • Рекомендуется сначала вычислять MAC, а затем производить шифрование сообщения вместе с MAC, а не наоборот.

5. Описание обмена сообщениями

Будем считать, что пользователи А и Б уже произвели согласование сеансового ключа и вычислили дочерние ключи, как это было описано в пункте 3.

Важно! Для каждого нового сеанса связи необходимо генерировать новые ключи шифрования и аутентификации.

Согласование ключей заслуживает написания отдельного поста, если не двух, потому сейчас мы его не рассматриваем.

Теперь А и Б обмениваются сообщениями в формате, подобном XML (либо следуют инструкциям, которые предусмотрены для иной ситуации, см пункт 4). Каждому сообщению присваивается уникальный 32-х битный номер. Нумерация сообщений начинается с единицы — так проще отследить момент, когда номера закончатся. Когда это произойдет, необходимо заново произвести согласование ключей. Можно использовать и 64-х битные номера сообщений, но тогда каждое сообщение станет на 4 байта длиннее. К тому же, время от времени следует производить обновление ключей, так что 32 бита — в самый раз.

Если для передачи данных используется протокол UDP, следует производить повторную посылку сообщения по некоторому таймауту в случае, если другая сторона на него не реагирует. При использовании TCP этого не требуется — транспортный уровень позаботиться о гарантированной доставке пакетов. Злоумышленник Е может нарушить сеанс связи между А и Б, испортив одно из сообщений или переставив пару сообщений местами. Но раз у нас есть «человек посередине», то он в любом случае может нарушить связь между А и Б. Отсюда вывод:

Не стоит тратить время на написание «своего TCP» поверх другого протокола — просто используйте протокол TCP. Если, конечно, в вашем приложении это возможно.

Когда пользователь А хочет послать сообщение mi, он вычисляет MAC этого сообщения ai согласно пункту 4. Затем он шифрует сообщение mi || ai, как это описано в пункте 2 и посылает пользователю Б следующее: i || m’i || a’i. После этого А увеличивает значение счетчика отправленных сообщений i на единицу.

Когда пользователь Б получает i || m’i || a’i, он расшифровывает mi и ai, проверяет код аутентификации. Сообщения с неверным MAC игнорируются (их мог отправить злоумышленник Е). Затем производится проверка значения i — если оно меньше ожидаемого номера сообщения, для определенности назовем его j, сообщение отбрасывается. В противном случае (i >= j) присваиваем j значение i + 1 и обрабатываем сообщение mi.

Здесь есть пара моментов, на которых стоит заострить внимание. Во-первых, как уже отмечалось в пункте 2, номера сообщений следует передавать в порядке байт от старшего к младшему. Во-вторых, проверку номера сообщения можно было бы проводить и до расшифровки. Но в этом случае, если сообщение с неверным номером пошлет злоумышленник Е, будет обнаружена (а затем записана в логи) ошибка «неверный номер сообщения», когда на самом деле произошла ошибка «неверный код аутентификации сообщения». Криптографы в первую очередь беспокоятся о безопасности и корректной работе приложений, а не о производительности. Некоторым программистам есть чему у них поучиться.

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

6. Заключение

Очень многие моменты не были рассмотрены в этом посте. Как А и Б согласуют сеансовые ключи связи? Откуда во время согласования ключей пользователю А известно, что он общается с Б, если по условию задачи Е может выдавать себя за кого угодно?

Дополнение: Эти вопросы рассматриваются в заметке, посвященной эллиптической криптографии. Также вас может заинтересовать статья Основные криптографические алгоритмы на языке Си.

Не менее интересные вопросы — как правильно генерировать псевдослучайные числа? Как противодействовать тайминг-атакам? Что делать, если операционная система поместит запущенное приложение со всеми ключами в файл подкачки? Как бесследно удалить файл с устаревшей парой RSA-ключей? Злоумышленник Е хоть и не знает содержимого сообщений, но знает их размер и время отправки. Опасно ли это и как этому противостоять?

Мир информационной безопасности неописуемо интересен, хоть и сложен. В этом блоге непременно появятся новые посты о криптографии.

Метки: , , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.