Блог → Идентификация удалённого пользователя без раскрытия пароля

Эта заметка на тему криптографии будет интересна, в первую очередь, сетевым программистам и веб-разработчикам, остальным же - можно почитать для общего развития. А можно и пропустить, решать вам. А мы - приступаем! О безопасном подключении к удалённым ресурсам написана уйма статей и книг, но по-прежнему появляются новые программные продукты, уязвимые к перехвату паролей. В открытом виде передают пароль стандартизованные протоколы FTP, POP, SNMP и сотни сетевых программ фирм-одиночек.

Передача вместо открытого пароля его контрольной суммы нисколько не меняет положение дел. Если по контрольной сумме невозможно за приемлемое время восстановить пароль - это всего лишь означает, что злоумышленнику для подключения не удастся воспользоваться стандартной клиентской программой. Но он всегда сможет собственноручно эмулировать последовательность пакетов данного протокола - и подставить в нужный пакет перехваченную контрольную сумму. Для пущего удобства можно даже провести сетевой поток от настоящей программы-клиента через локальный или выделенный маршрутизатор, исправляющий в потоке только "нужные" пакеты - это уже дело техники.

А ведь на передаче контрольных сумм построено все сетевое общение операционной системы Microsoft Windows и одна из схем авторизации протокола HTTP. И это при том, что криптография более 20 лет назад предложила метод защиты от подобной атаки с любым желаемым уровнем надежности.

Challenge handshake: идентификация без раскрытия пароля. Основная идея технологии "challenge handshake" крайне проста - при подключения клиента к удаленному серверу по незащищенному каналу, происходит буквально следующее:
• сервер генерирует случайное или псевдослучайное число - challenge, и отправляет его клиенту;
• клиент выполняет над challenge ка-кое-либо необратимое криптостойкое преобразование и отсылает результат серверу;
• сервер выполняет (на основе хранящейся у него копии пароля данного клиента) точно такое же преобразование и сверяет получившийся у него результат с полученным по сети ответом клиента;
• авторизация считается успешной, если два результата совпадают.

Давайте разберем приведённую схему чуть более подробно. Шаг нулевой: раздача ключей. Клиенту выдаётся пароль (если клиентская сторона представляет собой живого человека) или сразу ключ (если клиент - это программа, а ключ "зашит" где-нибудь в INI-файле) - чтобы снять необходимость преобразования "пароль -> ключ" в клиентском приложении. Challenge handshake, как и все другие криптопреобразования, уязвим к полному перебору паролей. А это означает, что:
1. ни пароль, ни ключ нельзя генерировать стандартными функциями ГПСЧ -их информационная емкость чрезвычайно мала, и
2. если пароль осмысленный, и его выбирает человек, то нужно соблюдать элементарные правила стойкости паролей (ну пожалуйста, не используйте свое имя или день рождения!).

Авторизация, шаг первый: генерация challenge. На первом шаге самым интересным моментом является собственно генерация случайного числа. Оказывается, в данном случае псевдослучайное число не обязательно должно быть хорошо рандомизированным. Главное - чтобы с достаточно большим уровнем надёжности это число сервер не выдал в запросе второй раз - чтобы злоумышленник не мог послать правильный ответ на запрос сервера, просто выловив из сети несколько пар пакетов "запрос-ответ". Чем выше заданный уровень надежности, и чем больше сеансов с клиентом вы хотите провести на одном и том же ключе - тем больше должна быть информационная емкость ГПСЧ. Так, если выбрана надежность 99%, то при проведении 100 сеансов без смены пароля генератор должен иметь ёмкость как минимум 19 бит. А, скажем, при традиционной ёмкости ГПСЧ в 32 бита порог смены пароля - 6000 сеансов.

Для того, чтобы расширить эти границы и полностью убрать необходимость в смене пароля - существуют две уловки. Во-первых, к сгенерированному случайному числу можно добавлять некоторую производную от текущего времени. Во-вторых, если ваш сервер запускается в единственном экземпляре, то на нём можно просто держать постоянно увеличивающийся счетчик подключений большой разрядности (например, 64 бита). Правда, добавлять к нему случайное число все-таки желательно - иначе вам придется хорошо продумать запись счетчика после каждого изменения в надёжную энергонезависимую память. Это нужно, чтобы после сбоя (например, по выключению питания) сервер не выдал в сеть в виде challenge уже запрошенное ранее значение счётчика, правильный ответ на которое злоумышленник мог перехватить.

Авторизация, шаг второй: криптопреобразование. Преобразование должно быть в первую очередь необратимо по известной паре "открытый/зашифрованный текст". Иначе третья сторона, определив ключ, сможет правильно отвечать на запросы. Но все стойкие шифры как раз и удовлетворяют этому условию. Следовательно, все что требуется - это зашифровать пришедший challenge выданным ключом и отправить результат на сервер. В качестве блочного шифра может быть выбран тот же TEA, рассмотренный нами ранее (см. предудующую заметку о криптографии). Перед шифрованием размер challenge нужно довести до размера, кратного блоку шифра (обычно он весь помещается в один блок - 64 или 128 бит). Расширение можно производить по любой схеме, лишь бы она была одинакова на сервере и на клиенте (можно выполнять его на сервере и высылать клиенту challenge, уже кратный размеру блока). Оказывается, блочные шифры - не единственное крипторешение для challenge handshake. Как отдельная отрасль криптографии давно развивается технология невосстановимых контрольных сумм - хеш-функций. Если в качестве входного блока данных хеш-функции будет взята любая комбинация challenge и пароля (лишь бы она строилась одинаково и на сервере и на клиенте), то получившееся в результате хеширования значение будет однозначно указывать серверу, знает или нет клиент секретный пароль. А поскольку хеш-функция необратима, то, перехватив challenge и отправленное клиентом обратно хеш-значение, злоумышленник ничего не сможет сделать, кроме как перебирать все пароли по словарю или по алфавиту.

По криптостойкости вариант с хеш-функцией ничем не отличается от блочного шифра. Это и неудивительно, так как в современных хеш-функциях применяются те же принципы работы, что и в блочных шифрах. Удобство хеширования в том, что нет необходимости предварительно преобразовывать пароль в ключ. То есть - и клиент, и сервер могут просто склеить challenge и строку пароля и вычислить хеш-сумму от получившейся строки. Как видим, здесь есть еще одно удобство: как challenge, так и пароль могут занимать произвольное количество байт.

Стандартом де-факто на сегодняшний день является хеш-функция «MD5» (Message Digest v.5), созданная Рональдом Ривестом (RSA Laboratories). Это достаточно трудоемкий в описании, но легко реализуемый алгоритм, ориентированный под 32-разрядные процессоры. Очень прост в реализации 8-битный проект-предшественник от этой же фирмы - хеш-функция MD2. Сейчас он не применяется из-за своей относительной медлительности, но нам-то нужно хешировать всего 10-30 байт, а не мегабайты (как для цифровой подписи), поэтому и MD2 вполне подойдет для этой задачи. Описания и исходные тексты обоих алгоритмов можно найти в доброй сотне источников; кроме того - они включены в сетевой информаторий RFC (MD2=RFC1319, MD5=RFC1321).

Симметричная аутентификация. Принцип, заложенный в challenge handshake, можно использовать не только для однократной идентификации пользователя в начале сеанса, но и для аутентификации каждой команды клиента серверу. Для этого к каждому сетевому пакету или блоку данных добавляется результат криптопреобразования его уникального номера. Обычно номер строится как объединение исходного challenge (играющего роль идентификатора сеанса) с порядковым номером пакета в данном сеансе. Это позволяет исключить подмену злоумышленником легитимного пользователя уже после этапа идентификации. Как и в challenge handshake, в симметричной аутентификации могут быть применены и блочные шифры, и хеш-функции. А сама схема работает гораздо быстрее и проще в реализации, чем традиционная асимметричная цифровая подпись. На этом пока и остановимся, и как обычно - продолжение следует!