Данный пост представляет собой что-то вроде конспекта, составленного по книге Брюса Шнайера и Нильса Фергюсона «Практическая криптография». Книга эта невероятно интересная! Если вы интересуетесь криптографией, то просто обязаны держать ее на полке (в крайнем случае — скачать электронную версию), ровно как и любую другую книгу этих авторов, которую сможете найти.
Важно! Если перед вами стоит практическая задача, советую найти настоящего специалиста по криптографии, а не полагаться на бложик никому неизвестного программиста. Учтите также, что книга была написана в 2005 году и к тому моменту, когда вы найдете эту заметку, информация может устареть.
Есть несколько причин, по которой я взялся за написание этой памятки. Для начала, я хочу иметь под рукой краткую заметку по созданию безопасного соединения. Есть подозрения, что когда-нибудь она мне пригодится. Объем «Практической криптографии» — более 400 страниц, так что на краткое руководство оно не тянет. Тем не менее, ознакомиться с книгой стоит для понимания того, почему авторы предлагают то решение, а не иное. Другая причина создания этого поста — мой экземпляр книги начинает потихоньку разваливаться. Третья — информация в интернете доступна отовсюду, а не только у меня дома.
Объяснение таких понятий, как блочный шифр, функция хэширования, цифровая подпись и тд в этом тексте вы не найдете. Обращайтесь к соответствующей литературе или Википедии. Если вы хотели бы увидеть в этом блоге посты из серии «основы криптографии», отпишитесь в комментариях — посмотрим, что тут можно сделать.
1. Постановка задачи
Есть пользователи А и Б, которые обмениваются сообщениями. Они могут использовать протокол TCP, UDP, электронную почту или чат — сейчас это не имеет значения. Есть злоумышленник Е, который может перехватывать, подменять и переставлять эти сообщения, заставлять их бесследно исчезнуть, а также слать сообщения от имени А и Б. Задача — помочь А и Б обмениваться сообщениями таким образом, чтобы Е не мог узнать их содержимого. Избавиться от Е никак нельзя.
2. Шифрование данных
В качестве алгоритма шифрования Шнайер и Фергюсон рекомендуют использовать AES (Rijndael с размером блока 128 бит ) с длиной ключа 256 бит в режиме CTR . В этом режиме максимальный размер одного сообщения составляет 16 · 2 32 байт. Вряд ли кого-то стеснит это ограничение. В крайнем случае можно разбить сообщение на части.
Для шифрования сообщения с номером i, имеющего размер L бит, необходимо вычислить L бит следующим способом:
b 1 , b 2 , …, b L = E K (0 [32 бита] || i [32 бита] || 0 [64 бита] ) || E K (1 || i || 0 ) ||
E K (2 || i || 0) || E K (3 || i || 0) || …
где функция E K (128 бит на выходе) — алгоритм шифрования, K (256 бит) — ключ шифрования, аргумент функции (128 бит) — шифруемые данные, а оператор || означает конкатенацию данных. После вычисления битов b 1 … b L складываем их по Жегалкину (исключающее ИЛИ, операция XOR, ⊕) с битами сообщения, в результате получаем зашифрованный текст. Для расшифровки нужно проделать точно такую же последовательность действий над шифротекстом.
Здесь важно обратить внимание на два момента. Во-первых, ключи шифрования при посылке данных от А к Б и от Б к А должны быть разными. Во-вторых, необходимо согласовать последовательность байт в аргументе функции E K . Порядок байт в 32-х разрядных числах может меняться в зависимости от архитектуры используемого процессора. Традиционно в сетевых протоколах и форматах файлов используется порядок байт от старшего к младшему .
3. Функция хэширования
В качестве функции хэширования авторы рекомендуют SHA-256 . Она используется при вычислении кодов аутентификации сообщений, а также при обмене ключами. В последнем случае применяется функция SHA d -256, определенная, как SHA256(SHA256(message)).
Допустим, мы успешно установили сеансовый ключ с помощью алгоритма Диффи-Хеллмана , в результате А и Б получили общий ключ К. Затем производится следующая последовательность действий:
// Избавляемся от алгебраической структуры ключа
K = SHA d -256(K)
// Строим 4 дочерних ключа:
// 1. Шифрование от А к Б
key_send_enc = SHA d -256(K || «Enc from A to B»)
// 2. Шифрование от Б к А
key_recv_enc = SHA d -256(K || «Enc from B to A»)
// 3. Аутентификация от А к Б
key_send_auth = SHA d -256(K || «Auth from A to B»)
// 4. Аутентификация от Б к А
key_recv_auth = SHA d -256(K || «Auth from B to A»)
4. Коды аутентификации сообщений
Каждое сообщение должно сопровождаться имитовставкой (MAC) . В качестве функции MAC Шнайер и Фергюсон рекомендуют использовать HMAC-SHA-256:
a i = HMAC K (i || L(x i ) || x i || m i )
HMAC K (m) = SHA256{(K ⊕ 0x5c5c..5c) || SHA256[(K ⊕ 0x3636..36) || m]}
Здесь a i — код аутентификации i-го сообщения m i . HMAC K (m) представляет собой хэш-функцию, значение которой зависит не только от сообщения m, но и от ключа K. Как и в случае с шифрованием, в MAC должно использоваться два независимых ключа для А и Б. Как уже отмечалось, ⊕ — это операция побитового исключающего ИЛИ (она же XOR).
В «Практической криптографии» делается особый акцент на том, как важно производить аутентификацию не только сообщения m i , являющегося обычной последовательностью байт, но и его смысла. Пусть m i = a || b || c, то есть состоит из нескольких полей определенной длины. Представим, что после очередного обновления протокола размеры полей изменилось. Тогда, если злоумышленник Е сможет подменить версию протокола, сообщение будет интерпретировано неверно. Притом неважно как злоумышленнику удастся это сделать — безопасность одной части системы не должна зависеть от безопасности остальных.
Вот для чего нужна дополнительная информация x i , включающая в себя id протокола (сигнатуру, IP:порт получателя и отправителя), версию протокола, id сообщения, размер и имена полей сообщения. Притом информация x i должна быть закодирована таким образом, чтобы ее можно было однозначно декодировать. L(x i ) в нашей формуле — это, очевидно, длина x i . К счастью, всего этого гемора можно избежать, просто передавая сообщения в XML-подобном формате данных, то есть таком, где дополнительная информация о смысле сообщения уже включена в само сообщение. Тут важно не забыть включить в каждое сообщение всю дополнительную информацию, перечисленную выше.
Что еще следует знать о MAC?
- Можно обрезать значение HMAC-SHA-256 до 16-и байт . Делать это не желательно, но такое решение лучше, чем использование HMAC-MD5 с целью уменьшить объем передаваемых данных.
- Рекомендуется сначала вычислять MAC, а затем производить шифрование сообщения вместе с MAC , а не наоборот.
5. Описание обмена сообщениями
Будем считать, что пользователи А и Б уже произвели согласование сеансового ключа и вычислили дочерние ключи, как это было описано в пункте 3.
Важно! Для каждого нового сеанса связи необходимо генерировать новые ключи шифрования и аутентификации.
Согласование ключей заслуживает написания отдельного поста, если не двух, потому сейчас мы его не рассматриваем.
Теперь А и Б обмениваются сообщениями в формате, подобном XML (либо следуют инструкциям, которые предусмотрены для иной ситуации, см пункт 4). Каждому сообщению присваивается уникальный 32-х битный номер. Нумерация сообщений начинается с единицы — так проще отследить момент, когда номера закончатся. Когда это произойдет, необходимо заново произвести согласование ключей. Можно использовать и 64-х битные номера сообщений, но тогда каждое сообщение станет на 4 байта длиннее. К тому же, время от времени следует производить обновление ключей, так что 32 бита — в самый раз.
Если для передачи данных используется протокол UDP, следует производить повторную посылку сообщения по некоторому таймауту в случае, если другая сторона на него не реагирует. При использовании TCP этого не требуется — транспортный уровень позаботиться о гарантированной доставке пакетов. Злоумышленник Е может нарушить сеанс связи между А и Б, испортив одно из сообщений или переставив пару сообщений местами. Но раз у нас есть «человек посередине», то он в любом случае может нарушить связь между А и Б. Отсюда вывод:
Не стоит тратить время на написание «своего TCP» поверх другого протокола — просто используйте протокол TCP. Если, конечно, в вашем приложении это возможно.
Когда пользователь А хочет послать сообщение m i , он вычисляет MAC этого сообщения a i согласно пункту 4. Затем он шифрует сообщение m i || a i , как это описано в пункте 2 и посылает пользователю Б следующее: i || m’ i || a’ i . После этого А увеличивает значение счетчика отправленных сообщений i на единицу.
Когда пользователь Б получает i || m’ i || a’ i , он расшифровывает m i и a i , проверяет код аутентификации. Сообщения с неверным MAC игнорируются (их мог отправить злоумышленник Е). Затем производится проверка значения i — если оно меньше ожидаемого номера сообщения, для определенности назовем его j, сообщение отбрасывается. В противном случае (i >= j) присваиваем j значение i + 1 и обрабатываем сообщение m i .
Здесь есть пара моментов, на которых стоит заострить внимание. Во-первых, как уже отмечалось в пункте 2, номера сообщений следует передавать в порядке байт от старшего к младшему. Во-вторых, проверку номера сообщения можно было бы проводить и до расшифровки. Но в этом случае, если сообщение с неверным номером пошлет злоумышленник Е, будет обнаружена (а затем записана в логи) ошибка «неверный номер сообщения», когда на самом деле произошла ошибка «неверный код аутентификации сообщения». Криптографы в первую очередь беспокоятся о безопасности и корректной работе приложений, а не о производительности. Некоторым программистам есть чему у них поучиться.
К сожалению, в случае активного вмешательства злоумышленника Е в передачу данных, пользователь Б может получить лишь подмножество сообщений, отправленных А. Если обнаружена пропажа сообщений, то поведение пользователей должно зависеть от типа информации, которой они обмениваются. В одних случаях пропажа части сообщений может быть не критичной, в другой — требовать прекращения сеанса связи.
6. Заключение
Очень многие моменты не были рассмотрены в этом посте. Как А и Б согласуют сеансовые ключи связи? Откуда во время согласования ключей пользователю А известно, что он общается с Б, если по условию задачи Е может выдавать себя за кого угодно?
Дополнение: Эти вопросы рассматриваются в заметке, посвященной эллиптической криптографии . Также вас может заинтересовать статья Основные криптографические алгоритмы на языке Си .
Не менее интересные вопросы — как правильно генерировать псевдослучайные числа? Как противодействовать тайминг-атакам? Что делать, если операционная система поместит запущенное приложение со всеми ключами в файл подкачки? Как бесследно удалить файл с устаревшей парой RSA-ключей? Злоумышленник Е хоть и не знает содержимого сообщений, но знает их размер и время отправки. Опасно ли это и как этому противостоять?
Мир информационной безопасности неописуемо интересен, хоть и сложен. В этом блоге непременно появятся новые посты о криптографии.