Аутентификация XMPP SASL SCRAM-SHA1

Недавно я смог настроить аутентификацию MD5 для потоков XMPP в Swift IOS, следуя инструкциям на следующих двух веб-сайтах (я использовал функцию CC-MD5 библиотеки Apple CommonCrypto C для фактического хэширования):

http://wiki.xmpp.org/web/SASLandDIGEST-MD5

http://www.deusty.com/2007/09/example-please.html

Я ищу похожее объяснение того, как заставить работать другие хешированные схемы аутентификации SASL, особенно SCRAM-SHA1. Я нашел официальный документ RFC5802, но у меня возникли проблемы с его пониманием (он также не относится к XMPP). Я был бы признателен за более простое объяснение или какой-нибудь простой читаемый код (C, PHP, C++, Javascript, Java), специфичный для аутентификации XMPP, который не использует библиотеки ни для чего, кроме реального хеширования.

Я заинтересован в понимании процесса и не хочу использовать ios XMPP-Framework. Любая помощь будет оценена.

1 ответ

Решение

SCRAM-SHA-1

Основной обзор того, как работает этот механизм:

  • Клиент отправляет имя пользователя, которое он хочет аутентифицировать как.
  • Сервер отсылает обратно соль для этого пользователя и количество итераций (либо генерируя их, либо просматривая их в своей базе данных на предмет заданного имени пользователя).
  • Клиент хэширует пароль с заданной солью для заданного количества итераций.
  • Клиент отправляет результат обратно.
  • Сервер выполняет хеширование и отправляет результат обратно клиенту, поэтому клиент также может проверить, что на сервере был пароль / хэш пароля.

Вам понадобятся криптографические алгоритмы: SHA-1, HMAC с SHA-1 и PBKDF2 с SHA-1. Вы должны посмотреть, как использовать их в вашем языке / фреймворке, так как я не рекомендую реализовывать их с нуля.

В деталях

  1. Сначала нормализуйте пароль (используя SASLprep), это будет normalizedPassword, Это сделано для того, чтобы кодировка UTF8 не могла содержать вариации одного и того же пароля.
  2. Выберите случайную строку (например, 32 шестнадцатеричных кодированных байта). Это будет clientNonce,
  3. initialMessage является "n=" .. username .. ",r=" .. clientNonce (Я использую .. для объединения строк).
  4. Клиент ожидает заголовок GS2 ("n,,") в initialMessage и base64-кодирует результат. Это отправляет это как первое сообщение:

    <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1">
        biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl
    </auth>
    
  5. Сервер отвечает на вызов. Данные запроса кодируются в base64:

    <challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
        cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY=
    </challenge>
    
  6. Клиент base64 декодирует его:

    r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
    
  7. Клиент анализирует это:

    • r= Это serverNonce, Клиент ДОЛЖЕН убедиться, что он начинается с clientNonce он отправил в своем первоначальном сообщении.
    • s= Это salt, закодировано в base64 (да, это закодировано в base64 дважды!)
    • i= Это количество итераций, i,
  8. Клиент вычисляет:

    clientFinalMessageBare = "c=biws,r=" .. serverNonce
    saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i)
    clientKey = HMAC-SHA-1(saltedPassword, "Client Key")
    storedKey = SHA-1(clientKey)
    authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare
    clientSignature = HMAC-SHA-1(storedKey, authMessage)
    clientProof = clientKey XOR clientSignature
    serverKey = HMAC-SHA-1(saltedPassword, "Server Key")
    serverSignature = HMAC-SHA-1(serverKey, authMessage)
    clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof)
    
  9. Клиент base64 кодирует clientFinalMessage и отправляет его в ответ:

    <response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
        Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0=
    </response>
    
  10. Если все прошло хорошо, вы получите <success> ответ от сервера:

     <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
         dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289
     </success>
    
  11. Base64 расшифровывается это содержит:

     v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
    
  12. Клиент ДОЛЖЕН удостовериться, что значение v это кодировка base64 serverSignature,

Дополнительно

Это базовая версия алгоритма. Вы можете расширить его, чтобы сделать:

  • Привязка канала. Это смешивает некоторую информацию от соединения TLS с процедурой, чтобы предотвратить атаки MitM.
  • Хешированное хранилище. Если сервер всегда отправляет один и тот же salt а также i значения, то клиент может хранить только saltedPassword вместо пароля пользователя. Это более безопасно (так как клиенту не нужно хранить пароль, просто трудно перевернуть соленый хеш) и быстрее, так как клиенту не нужно каждый раз выполнять растяжение ключа.

    Сервер также может использовать хешированное хранилище: сервер может хранить только salt, i, storedKey а также serverKey, Больше информации об этом здесь.

  • Возможно, также добавление SCRAM-SHA-256 (хотя поддержка сервера кажется несуществующей).

Ловушки

Некоторые распространенные подводные камни:

  • Не предполагайте ничего о длине одноразовых или salt (хотя, если вы генерируете их, убедитесь, что они достаточно длинные и криптографически случайные).
  • salt кодируется в base64 и может содержать любые данные (встроенные NULс).
  • Отказ от использования SASLprep может хорошо работать для людей, использующих пароли ASCII, но он может полностью прервать вход в систему для людей, использующих другие сценарии.
  • initialMessage часть authMessage не включает заголовок GS2 (в большинстве случаев это "n,,").

Тестовые векторы

Если вы хотите проверить свою реализацию, вот все промежуточные результаты для примера из RFC:

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

  • Пароль: pencil

  • Клиент генерирует случайный одноразовый номер fyko+d2lbbFgONRv9qkxdawL

  • Начальное сообщение: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL

  • Сервер генерирует случайный одноразовый номер 3rfcNHYJY1ZVvWVs7j

  • Сервер отвечает: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096

  • Соль (гекс): 4125c247e43ab1e93c6dff76

  • Окончательное сообщение клиента: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j

  • Соленый пароль (шестнадцатеричный): 1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d

  • Ключ клиента (шестнадцатеричный): e234c47bf6c36696dd6d852b99aaa2ba26555728

  • Сохраненный ключ (шестнадцатеричный): e9d94660c39d65c38fbad91c358f14da0eef2bd6

  • Auth message: n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j

  • Подпись клиента (шестнадцатеричная): 5d7138c486b0bfabdf49e3e2da8bd6e5c79db613

  • Доказательство клиента (шестнадцатеричное) bf45fcbf7073d93d022466c94321745fe1c8e13b

  • Ключ сервера (шестнадцатеричный): 0fe09258b3ac852ba502cc62ba903eaacdbf7d31

  • Подпись сервера (шестнадцатеричная): ae617da6a57c4bbb2e0286568dae1d251905b0a4

  • Заключительное сообщение клиента: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=

  • Последнее сообщение сервера: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=

  • Подпись сервера (шестнадцатеричная): ae617da6a57c4bbb2e0286568dae1d251905b0a4

Другие вопросы по тегам