Создание ключа авторизации

Формат запроса описывается с использованием бинарных данных Serialization и TL Language. Все большие числа передаются как строки, содержащие требуемую последовательность байтов в большом эндианном порядке. Хэш-функции, такие как SHA1, возвращающие строки (20 байтов), которые также могут быть интерпретированы как большие числа endian. Небольшие номера (intlongint128int256) обычно являются небольшими эндианами; однако, если они являются частью SHA1, байты не переставляются. В этом случае, если longxэто 64 бита нижнего порядка SHA1 из строки s, затем последние 8 байтов 20-байтной струны SHA1(s)Они берутся и интерпретируются как 64-битное целое число.

Перед отправкой незашифрованных сообщений (требуется в данном случае для генерации ключа авторизации) клиент должен пройти (p,q) авторизацию следующим образом.

Инициация обмена DH

1) Клиент отправляет запрос на сервер
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;

Значение nonce выбирается клиентом случайным образом (случайное число) и идентифицирует клиента в этом сообщении. Следуя этому шагу, он всем известен.

2) Сервер отправляет ответ формы
resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ;

Здесь струнный pq представляет собой представление натурального числа (в двоичном большом формате endian). Это число является продуктом двух разных нечетных простых чисел. Обычно pq меньше или равен 2^63-1. Значение server_nnce выбирается сервером случайным образом; следуя этому шагу, он известен всем.

server_public_key_fingerprintsпредставляет собой список открытых отпечатков пальцев RSA (64 бита нижнего порядка SHA1 (server_public_key); открытый ключ представлен как голый тип rsa_public_key n:string e:string = RSAPublicKey, где, как обычно, n и e являются числами в большом эндиановом формате, сериализованными как строки байт, после чего SHA1 вычисляется) получен сервером.

Все последующие сообщения содержат пару (nonce, server_nonnce) как в простом тексте, так и в зашифрованных частях, что позволяет идентифицировать «временный сеанс» — один прогон протокола генерации ключа, описанного на этой странице, который использует одну и ту же (nonce, server_nnce) пару. Злоумышленник не мог создать параллельный сеанс с сервером с теми же параметрами и повторно использовать части зашифрованных сервером или клиентом сообщений для своих собственных целей в таком параллельном сеансе, потому что сервер для любого нового «временного сеанса» будет выбран другой сервером.

Доказательство работы

3) Клиент разлагает pq на простые факторы, такие, что p < q.

Это начинает раунд обмена ключами Диффи-Хеллмана.

Представление доказательств работы; аутентификация сервера

4) encrypted_data Полезная нагрузка генерация

Прежде всего, порождать encrypted_dataПолезная нагрузка следующим образом:

  • new_nonce := другое (хорошее) случайное число, сгенерированное клиентом; после этого запроса он известен как клиенту, так и серверу;

  • данные := сериализация

    p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;

    или из

    p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;

    где dcэто идентификатор DC, с которым мы разговариваем; 10000Он должен быть добавлен в DC ID при подключении к тестовым серверам; он должен быть отрицательным, если DC, к которому мы подключаемся, является медиа (не CDN) DC.

  • crypted_data := RSA_PAD (data, server_public_key), где RSA_PAD является версией RSA с вариантом набивки OAEP+, описанным ниже в 4.1).

Кто-то может перехватить запрос и заменить его своим собственным, самостоятельно разлагая pq на факторы вместо клиента. Единственное поле, которое имеет смысл изменить, - это new_nonce, который был бы тем, которое нарушить должен был бы регенерировать (потому что злоумышленник не может расшифровать зашифрованные данные, отправленные клиентом). Поскольку все последующие сообщения шифруются с помощью new_nonce или содержат new_nonce_hash, они не будут обрабатываться клиентом (нарушитель не сможет заставить его выглядеть так, как будто они были сгенерированы сервером, потому что они не будут содержать new_nonce). Таким образом, этот перехват приведет только к тому, что злоумышленник завершит протокол генерации ключа авторизации вместо клиента и создаст новый ключ (который не имеет ничего общего с клиентом); однако тот же эффект может быть достигнут просто путем создания нового ключа от имени.

Альтернативная форма внутренних данных (p_q_inner_data_temp_dc) используется для создания временных ключей, которые хранятся только в RAM сервера и отбрасываются после максимума expires_inСекунды. Сервер может свободно отбрасывать свою копию раньше. Во всех остальных отношениях временный протокол генерации ключа одинаков. После создания временного ключа клиент обычно связывает его с основным ключом авторизации с помощью метода auth.bindTempAuthKey и использует его для всех клиент-серверных сообщений до истечения срока его действия; затем генерируется новый временный ключ. Таким образом, достигается совершенная форвардная секретность (PFS) в клиент-серверной коммуникации. Читать больше о PFS »

4.1) RSA_PAD(data, server_public_key) Вышеуказанные выше реализованы следующим образом:
  • data_with_padding := данные + random_padding_bytes; -- где выбран random_padding_bytes, поэтому полученная длина data_with_padding составляет ровно 192 байт, а данные - это TL-сериализованные данные, которые должны быть зашифрованы, как и раньше. Нужно проверить, что данные не длиннее 144 байтов.
  • data_pad_reversed := BYTE_REVERSE(data_with_padding); -- получается из data_with_padding путем обращения в порядок байт.
  • генерируется случайный 32-байт temp_key.
  • data_with_hash := data_pad_reversed + SHA256(temp_key + data_with_padding); -- после этого назначения data_with_hash составляет ровно 224 байт.
  • aes_encrypted := AES256_IGE(data_with_hash, temp_key, 0); -- AES256-IGE шифрование с нулем IV.
  • temp_key_xor := temp_key XOR SHA256(aes_encrypted); -- скорректированный ключ, 32 байт
  • key_aes_encrypted := temp_key_xor + aes_encrypted; -- ровно 256 байт (2048 бит) длиной
  • Значение key_aes_encrypted сравнивается с RSA-модулем server_pubkey как большой-эндиан 2048-битный (256-байт) неподписанный целое число. Если key_aes_encrypted оказывается больше или равен модулю RSA, предыдущие шаги, начиная с генерации нового случайного temp_key, повторяются. В противном случае выполняется заключительный этап:
  • Зашифровано_data := RSA(key_aes_encrypted, server_pubkey); -- 256-байтовый большой-эндианный целочисленный возведен в необходимую мощность из модуля открытого ключа RSA модуля RSA, и результат хранится как большое целое число, состоящее ровно из 256 байтов (с ведущим нулевым байтом, если требуется).
5) Отправить запрос req_DH_params сгенерированным encrypted_data
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_dаta:string = Server_DH_Params
6) Сервер отвечает:
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;

Если запрос неправильный, сервер возвращает -404ошибка и рукопожатие должны быть перезапущены (любой последующий запрос также возвращается) -404Даже если это правильно).
А -444Ошибка также может быть возвращена, если тест DC ID передан в p_q_inner_data_(_temp)dcпри рукопожатии с производством DC, и наоборот.

Здесь crypted_answer получается следующим образом:

  • new_nonce_hash := 128 битов более низкого порядка SHA1 (new_nnce);
  • Ответ := сериализация
    server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
  • reply_with_hash := SHA1(ответ) + ответ + (0-15 случайных байтов); так что длина делится на 16;
  • tmp_aes_key := SHA1(new_nnce + server_nonce) + substr (SHA1(server_nonce + new_nonce), 0, 12);
  • tmp_aes_iv := substr (SHA1(server_nonce + new_nonce), 12, 8) + SHA1(new_nonce + new_nonce) + substr (new_nnce, 0, 4);
  • Зашифрованно_answer := AES256_ige_encrypt (answer_with_hash, tmp_aes_key, tmp_aes_iv); здесь tmp_aes_key - это 256-битный ключ, а tmp_aes_iv - 256-битный вектор инициализации. Как и во всех других случаях, использующих шифрование AES, зашифрованные данные дополняются случайными байтами на длину, делимую на 16 непосредственно перед шифрованием.

Следуя этому шагу, new_nonce по-прежнему известен только клиенту и серверу. Клиент уверен, что это сервер, который ответил, и что ответ был сгенерирован специально в ответ на клиентский запрос req_DH_params, поскольку данные ответов шифруются с помощью new_nnce.

Ожидается, что клиент проверит, является ли p = dh_prime безопасным 2048-битным простым (что означает, что как p, так и (p-1)/2 являются простыми, и что 2^2047 < p < 2^2048), и что g генерирует циклическую подгруппу прайм-порядка (p-1)/2, т.е. является квадратичным остатком mod p. Поскольку g всегда равен 2, 3, 4, 5, 6 или 7, это легко сделать с использованием квадратарного закона взаимности, давая простое условие на p mod 4g, а именно, p mod 8 = 7 для g = 2; p mod 3 = 2 для g = 3; нет дополнительного условия для g = 4; p mod 5 = 1 или 4 для g = 5 ; p mod 24 = 19 или 23 для g = 6 p mod 7 = 3, 5 or 6g = 7; p После того, как g и p были проверены клиентом, имеет смысл кэшировать результат, чтобы не повторять длительные вычисления в будущем.

Если проверка занимает слишком много времени (что имеет место для старых мобильных устройств), можно было бы первоначально запустить только 15 итераций Миллера-Рабина для проверки первобытности p и (p - 1)/2 с вероятностью ошибки, не превышающей одну миллиардную, и сделать больше итераций позже на заднем плане.

Другая оптимизация заключается в том, чтобы встроить в клиентский код приложения небольшую таблицу с некоторыми известными «хорошими» парами (g,p) (или просто известными безопасными простыми pp, поскольку условие на g легко проверяется во время выполнения), проверенных во время фазы генерации кода, чтобы вообще избежать такой проверки во время выполнения. Сервер редко меняет эти значения, поэтому обычно приходится помещать текущее значение dh_prime сервера в такую таблицу. Например, текущее значение dh_prime равны (в большом-эндианском байт-порядке)

7) Клиент вычисляет случайный 2048-битный номер b (используя достаточное количество энтропии) и отправляет серверу сообщение
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_dаta:string = Set_client_DH_params_answer;

Здесь шифрованные_данные получают таким образом:

  • g_b := pow(g, b) mod dh_prime;
  • данные := сериализация
    client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data
  • data_with_hash := SHA1(data) + data + (0-15 случайных байтов); так что длина делится на 16;
  • Зашифровано_data := AES256_ige_encrypt (data_with_hash, tmp_aes_key, tmp_aes_iv);

Поле retry_id равно нулю во время первой попытки; в противном случае оно равно auth_key_aux_hash от предыдущей неудачной попытки (см. Пункт 9).

8) После этого auth_key равны pow(g, {ab}) mod dh_prime; на сервере, он вычисляется как pow(g_b, a) mod dh_prime, и на клиента как (g_a)^b mod dh_prime.

Auth_key_hash вычисляется := 64 биты нижнего порядка SHA1 (auth_key). Сервер проверяет, есть ли уже другой ключ с тем же auth_key_hash, и отвечает одним из следующих способов.

DH обмен ключами завершен

9) Сервер отвечает одним из трех способов:
dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
  • new_nonce_hash1, new_nonce_hash2, и new_nonce_hash3 получаются как 128 бит нижнего порядка SHA1 из строки байта, полученной из строки new_nonce, путем добавления одного байт со значением 1, 2, или 3, а затем еще 8 байтов с auth_key_aux_hash. Различные значения необходимы, чтобы нарушитель не изменил реакцию dh_gen_ok сервера на dh_gen_retry.
  • auth_key_aux_hash - это 64 биты более высокого порядка SHA1(auth_key). Его не следует путать с auth_key_hash.

В другом случае клиент переходит к пункту 7), генерируя новый b. В первом случае клиент и сервер договорились auth_key, после чего забывают все другие временные данные, и клиент создает еще один зашифрованный сеанс с помощью auth_key. При этом сервер_salt изначально установлен на substr(new_nonce, 0, 8) XOR substr(server_nonce, 0, 8). При необходимости клиент хранит разницу между сервером-временем, полученным в 5) и его местным временем, чтобы всегда иметь хорошее приближение времени сервера, которое требуется для генерации правильных идентификаторов сообщений.

ВАЖНО: Помимо условий на Prime dh_prime и генераторе Diffie-Hellman и генератор g, обе стороны должны проверить, что g, g_a и g_b больше, чем 1 и меньше, чем dh_prime - 1. Мы рекомендуем проверить, что g_a и g_b находятся между 2^{2048-64} и dh_prime - 2^{2048-64}.

Обработка ошибок (потерянные запросы и ответы)

Если клиент не получает никакого ответа на свой запрос от сервера в течение определенного промежутка времени, он может просто повторно отправить запрос. Если сервер уже отправил ответ на этот запрос (точно такой же запрос и не просто похожий: все параметры во время повторного запроса должны принимать одни и те же значения), но он не попал к клиенту, сервер просто повторно отправит тот же ответ. Сервер запоминает ответ в течение 10 минут после получения запроса в 1). Если сервер уже забыл ответ или необходимые временные данные, клиенту придется начинать с самого начала.

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

Пример использования

Пример полного списка запросов, необходимых для генерации ключа авторизации, показан на отдельной странице.

Наверх