Аутентификация 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. Вы должны посмотреть, как использовать их в вашем языке / фреймворке, так как я не рекомендую реализовывать их с нуля.
В деталях
- Сначала нормализуйте пароль (используя SASLprep), это будет
normalizedPassword
, Это сделано для того, чтобы кодировка UTF8 не могла содержать вариации одного и того же пароля. - Выберите случайную строку (например, 32 шестнадцатеричных кодированных байта). Это будет
clientNonce
, initialMessage
является"n=" .. username .. ",r=" .. clientNonce
(Я использую..
для объединения строк).Клиент ожидает заголовок GS2 (
"n,,"
) в initialMessage и base64-кодирует результат. Это отправляет это как первое сообщение:<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1"> biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl </auth>
Сервер отвечает на вызов. Данные запроса кодируются в base64:
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY= </challenge>
Клиент base64 декодирует его:
r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
Клиент анализирует это:
r=
ЭтоserverNonce
, Клиент ДОЛЖЕН убедиться, что он начинается сclientNonce
он отправил в своем первоначальном сообщении.s=
Этоsalt
, закодировано в base64 (да, это закодировано в base64 дважды!)i=
Это количество итераций,i
,
Клиент вычисляет:
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)
Клиент base64 кодирует
clientFinalMessage
и отправляет его в ответ:<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0= </response>
Если все прошло хорошо, вы получите
<success>
ответ от сервера:<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289 </success>
Base64 расшифровывается это содержит:
v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
Клиент ДОЛЖЕН удостовериться, что значение
v
это кодировка base64serverSignature
,
Дополнительно
Это базовая версия алгоритма. Вы можете расширить его, чтобы сделать:
- Привязка канала. Это смешивает некоторую информацию от соединения 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