Pull to refresh

Comments 4

Поздравляю с первой статьёй. Как фидбек - попробуйте объяснения работы алгоритмов делать более понятными для не криптографов и киберсек. спецов.

Например, чтобы понять механизм работы, мне пришлось RFC скормить Gemini и попросить пошагово объяснить. Вот что вышло для SCRAM-SHA-256-PLUS (луддитам не читать спойлер - там богомерзкое объяснение от ЛЛМ):

Скрытый текст

Исходные данные: Состояние перед аутентификацией

  • Клиент: Знает username (имя пользователя) и password (пароль). Больше у него ничего нет.

  • Сервер: НЕ знает пароль пользователя. В его базе данных для каждого username хранится запись с четырьмя ключевыми, заранее вычисленными элементами:

    1. Salt (Соль): Уникальная случайная строка, сгенерированная при создании учётной записи.

    2. IterationCount (Число итераций): Число (например, 4096, но для защиты от офлайн-атак рекомендуется 600 000+). Это "фактор сложности".

    3. StoredKey: Хеш ключа клиента. Конкретно, StoredKey = H(ClientKey).

    4. ServerKey: Ключ, который сервер будет использовать для создания собственных подписей.

    Ключи StoredKey и ServerKey вычисляются из SaltedPassword (пароль + соль) только один раз — при регистрации пользователя или смене пароля. Сервер хранит эти готовые ключи, чтобы избежать дорогостоящих вычислений при каждом входе в систему.

Криптографические функции, которые мы будем использовать:

  • H(): Хеш-функция SHA-256.

  • HMAC(key, data): Функция HMAC-SHA-256.

  • Hi(): Функция PBKDF2-HMAC-SHA-256, которая представляет собой HMAC, повторённый IterationCount раз. Это "медленная" часть.

  • XOR: Операция "побитовое исключающее ИЛИ".

Полный "танец" аутентификации: Шаг за шагом

Вот весь процесс, сообщение за сообщением.

Шаг 1: Первое сообщение клиента (Начало диалога)

Клиент инициирует диалог, не раскрывая никакой конфиденциальной информации.

Клиент -> Сервер: n,,n=user,r=rOprNGfwEbeRWgbNEkqO

Что происходит "под капотом":

  1. Генерация Nonce клиента (c-nonce): Клиент создаёт криптографически случайную строку (r=...), уникальную для каждой попытки аутентификации.

  2. Форматирование сообщения:

    • n=user: Имя пользователя.

    • n,,: Заголовок gs2-header. В примере из RFC n означает, что клиент не поддерживает привязку к каналу, но для -PLUS обмена здесь было бы y,,. Первая запятая означает, что клиент поддерживает привязку к каналу, вторая — что данные привязки будут отправлены позже.

    • r=...: Уникальное nonce-значение клиента.

Чего достигает этот шаг: Сигнализирует о начале аутентификации, идентифицирует пользователя и предоставляет уникальное значение для предотвращения атак повторного воспроизведения со стороны сервера.

Шаг 2: Первое сообщение сервера (Вызов)

Сервер отвечает, предоставляя публичные параметры, необходимые клиенту.

Сервер -> Клиент: r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096

Что происходит "под капотом":

  1. Поиск пользователя: Сервер находит запись для user. Если пользователь не найден, сервер может сгенерировать поддельные данные, чтобы не раскрывать факт существования или отсутствия пользователя.

  2. Генерация и объединение Nonce: Сервер генерирует собственное nonce-значение (s-nonce) и добавляет его в конец nonce-значения клиента. Полученный r=... теперь содержит случайность от обеих сторон.

  3. Извлечение публичных данных: Сервер извлекает из своей базы данных Salt (s=...) и IterationCount (i=...) для данного пользователя.

  4. Сборка и отправка: Сервер отправляет объединённое nonce-значение, соль и число итераций.

Чего достигает этот шаг: Бросает клиенту вызов (challenge) доказать знание пароля, используя предоставленные параметры, и гарантирует уникальность сессии.

Шаг 3: Финальное сообщение клиента (Тяжёлая работа и доказательство)

Это самый сложный шаг, на котором клиент выполняет всю основную криптографическую работу.

Клиент -> Сервер: c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=

Что происходит "под капотом" (по порядку):

  1. Вычисление SaltedPassword ("медленная" часть): Клиент выполняет ресурсоёмкую функцию PBKDF2:

    • SaltedPassword = Hi(password, salt, iterationCount)

    • Это тот самый шаг, который делает офлайн-атаки на украденную базу данных очень медленными и дорогими.

  2. Вывод специализированных ключей (Криптографическая гигиена): Клиент использует SaltedPassword как мастер-ключ для создания двух разных ключей для разных целей. Для этого используются публичные, стандартные строки-константы, определённые в спецификации SCRAM.

    • ClientKey = HMAC(SaltedPassword, "Client Key")

    • ServerKey = HMAC(SaltedPassword, "Server Key")

    • Теперь у клиента есть два ключа: ClientKey для создания своего доказательства и ServerKey для последующей проверки сервера.

  3. Получение данных привязки к каналу (часть -PLUS): Клиент запрашивает у своего TLS-уровня уникальный "отпечаток" соединения (channel-binding-data) и форматирует его в часть сообщения c=biws.

  4. Формирование Auth-Message: Клиент создаёт единую строку, представляющую всю историю диалога:

    • Auth-Message = client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof

    • Эта строка уникальна благодаря nonce и привязана к TLS-каналу благодаря данным привязки.

  5. Создание ClientProof (Доказательство клиента):

    • StoredKey = H(ClientKey) (Клиент вычисляет это значение "на лету"; ему не нужно его хранить)

    • ClientSignature = HMAC(StoredKey, Auth-Message)

    • ClientProof = ClientKey XOR ClientSignature

  6. Сборка и отправка: Клиент кодирует ClientProof в base64 и отправляет его как p=....

Чего достигает этот шаг: Клиент доказывает, что он вычислил правильный ClientKey (и, следовательно, знает пароль), не отправляя его. Доказательство криптографически привязано к данной сессии (через nonce) и к данному TLS-соединению (через привязку к каналу).

Шаг 4: Финальное сообщение сервера (Проверка и взаимное доказательство)

Сервер проверяет доказательство клиента и, если оно верно, доказывает собственную подлинность.

Сервер -> Клиент: v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=

Что происходит "под капотом":

  1. Разбор и проверка: Сервер проверяет nonce и получает данные привязки к каналу со своей стороны TLS-соединения. Это момент истины для -PLUS: если есть "человек посередине", отпечатки каналов не совпадут.

  2. Воссоздание Auth-Message: Он реконструирует в точности ту же строку Auth-Message, что и клиент.

  3. Восстановление ClientKey и проверка доказательства:

    • Сервер извлекает из базы данных StoredKey для этого пользователя.

    • Он вычисляет, какой должна быть подпись клиента: ClientSignature = HMAC(StoredKey, Auth-Message).

    • Он восстанавливает ключ клиента из доказательства: ClientKey_from_client = ClientProof XOR ClientSignature.

    • Он проверяет ключ: H(ClientKey_from_client) ДОЛЖЕН быть равен сохранённому StoredKey. Если совпадает, клиент аутентифицирован.

  4. Создание ServerSignature ("эффективная" часть):

    • Сервер извлекает из своей базы данных заранее вычисленный ServerKey для этого пользователя. (Никаких дорогостоящих вычислений здесь не требуется).

    • Он подписывает диалог: ServerSignature = HMAC(ServerKey, Auth-Message).

  5. Сборка и отправка: Сервер кодирует ServerSignature в base64 и отправляет как верификатор v=....

Чего достигает этот шаг: Эффективно проверяет подлинность клиента и предоставляет доказательство собственной подлинности, завершая взаимную аутентификацию.

Шаг 5: Финальная проверка клиента

Клиент должен проверить сервер, чтобы защититься от самозванцев.

Что происходит "под капотом":

  1. Вычисление ожидаемой ServerSignature: Клиент использует ServerKey, вычисленный на Шаге 3, чтобы рассчитать, какой должна быть подпись сервера: ExpectedServerSignature = HMAC(ServerKey, Auth-Message).

  2. Сравнение: Он сравнивает свою вычисленную подпись со значением v=..., полученным от сервера.

  3. Успех или провал: Если они совпадают, сервер подлинный. Если нет, клиент ОБЯЗАН немедленно разорвать соединение.

Чего достигает этот шаг: Защищает клиента от атаки "человек посередине" (Man-in-the-Middle), выполняя обещание взаимной аутентификации.


А тут объяснение (тоже от ЛЛМ) как защищает от MiTM -PLUS:

Скрытый текст

Сценарий атаки MITM (без -PLUS)

Представим классическую атаку MITM:

  1. Клиент -> Злоумышленник: Клиент думает, что подключается к Серверу, но на самом деле устанавливает идеальное, зашифрованное TLS-соединение со Злоумышленником. Назовём это Канал А.

  2. Злоумышленник -> Сервер: Злоумышленник, притворяясь Клиентом, устанавливает второе, такое же идеальное TLS-соединение с настоящим Сервером. Назовём это Канал Б.

Теперь Злоумышленник сидит "посередине" и просто пересылает сообщения SCRAM туда-сюда:

  • Клиент отправляет client-first-message по Каналу А.

  • Злоумышленник расшифровывает, видит его и пересылает по Каналу Б на Сервер.

  • Сервер отвечает server-first-message по Каналу Б.

  • Злоумышленник пересылает его по Каналу А Клиенту.

  • Клиент вычисляет ClientProof и отправляет его.

  • Злоумышленник пересылает его на Сервер.

Результат: Сервер успешно проверяет ClientProof. Он считает, что у него установлено защищённое соединение с Клиентом. Но на самом деле он общается со Злоумышленником. Атака удалась.

Как -PLUS (Привязка к каналу) ломает эту атаку

Вот где собака зарыта. Привязка к каналу заставляет аутентификацию SCRAM "знать" о том TLS-канале, внутри которого она происходит.

Давайте пройдём по тому же сценарию, но теперь с SCRAM-SHA-256-PLUS.

  1. У нас та же ситуация: Клиент <-> Канал А <-> Злоумышленник <-> Канал Б <-> Сервер.

  2. Шаг 3 (Клиент): Когда Клиент готовится создать своё доказательство (ClientProof), он делает дополнительное действие:

    • Он обращается к своему TLS-уровню и просит: "Дай мне уникальный криптографический отпечаток именно этого TLS-соединения". Этот отпечаток (channel-binding-data) вычисляется на основе секретов, которыми обменялись стороны во время TLS-рукопожатия.

    • Для Клиента это будет отпечаток Канала А.

    • Клиент включает этот отпечаток Канала А в строку Auth-Message, которую он подписывает.

  3. Теперь ClientProof криптографически зависит не только от пароля и nonce, но и от уникальной идентичности Канала А. Клиент отправляет это доказательство.

  4. Злоумышленник, как и раньше, перехватывает это сообщение и пересылает его по Каналу Б на Сервер.

  5. Шаг 4 (Сервер): Момент истины.

    • Сервер получает ClientProof. Чтобы его проверить, он должен сам вычислить, каким это доказательство должно быть.

    • Он, так же как и клиент, обращается к своему TLS-уровню и просит: "Дай мне уникальный отпечаток этого TLS-соединения".

    • Но для Сервера это соединение — Канал Б!

    • Поскольку TLS-рукопожатия для Канала А (Клиент <-> Злоумышленник) и Канала Б (Злоумышленник <-> Сервер) были двумя совершенно разными, независимыми событиями, их криптографические отпечатки гарантированно будут разными.

    • Сервер вычисляет ожидаемое доказательство, используя отпечаток Канала Б.

  6. Провал проверки:

    • Доказательство, полученное от Клиента, было подписано с использованием отпечатка Канала А.

    • Доказательство, которое ожидает увидеть Сервер, должно быть подписано с использованием отпечатка Канала Б.

    • Эти два значения не совпадают. Проверка проваливается. Сервер отклоняет попытку входа.

Результат: Атака полностью провалилась.

Аналогия

Представьте, что вы и ваш друг договорились о секретном рукопожатии (это SCRAM).

  • Без -PLUS: Вы делаете рукопожатие с мошенником. Мошенник в точности повторяет его с вашим другом. Друг отвечает, мошенник повторяет ответ вам. Всё выглядит нормально.

  • С -PLUS: Ваше новое рукопожатие включает дополнительный шаг: вы должны вместе указать на уникальную трещину на полу в той комнате, где вы находитесь (это отпечаток канала).

    • Вы находитесь в Комнате А с мошенником и указываете на трещину в ней.

    • Мошенник пытается передать это вашему другу, который находится в Комнате Б.

    • Ваш друг смотрит на пол в своей комнате и видит другую трещину (или её отсутствие). Он мгновенно понимает, что вы не в одной комнате, и что-то не так.

Итог: Механизм -PLUS не просто защищает данные внутри канала. Он криптографически "приваривает" саму аутентификацию к конечным точкам этого конкретного канала, делая невозможной атаку с "прозрачной" ретрансляцией. Он защищает именно от MITM-атаки, запущенной с самого начала.

Спасибо. Насчет объяснения алгоритмов нужно подумать над тем, как бы не переборщить и не превратить объяснение в отдельную статью. А так, подумаю, как можно реализовать объяснение непогруженным людям.

А есть еще OTR (который уже признан устаревшим, хотя вполне эффективен) и OMEMO.

Это алгоритмы шифрования сообщений, а не аутентификации.

Sign up to leave a comment.

Articles