Формат запроса описывается с использованием бинарных данных Serialization и TL Language. Все большие числа передаются как строки, содержащие требуемую последовательность байтов в большом эндианном порядке. Хэш-функции, такие как SHA1, возвращающие строки (20 байтов), которые также могут быть интерпретированы как большие числа endian. Небольшие номера (int, long, int128, int256) обычно являются небольшими эндианами; однако, если они являются частью SHA1, байты не переставляются. В этом случае, если longxэто 64 бита нижнего порядка SHA1 из строки s, затем последние 8 байтов 20-байтной струны SHA1(s)Они берутся и интерпретируются как 64-битное целое число.
Перед отправкой незашифрованных сообщений (требуется в данном случае для генерации ключа авторизации) клиент должен пройти (p,q) авторизацию следующим образом.
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;Значение nonce выбирается клиентом случайным образом (случайное число) и идентифицирует клиента в этом сообщении. Следуя этому шагу, он всем известен.
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) пару. Злоумышленник не мог создать параллельный сеанс с сервером с теми же параметрами и повторно использовать части зашифрованных сервером или клиентом сообщений для своих собственных целей в таком параллельном сеансе, потому что сервер для любого нового «временного сеанса» будет выбран другой сервером.
Это начинает раунд обмена ключами Диффи-Хеллмана.
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 »
RSA_PAD(data, server_public_key) Вышеуказанные выше реализованы следующим образом:encrypted_datareq_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_dаta:string = Server_DH_Paramsserver_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 получается следующим образом:
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;Следуя этому шагу, 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 равны (в большом-эндианском байт-порядке)
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_dаta:string = Set_client_DH_params_answer;Здесь шифрованные_данные получают таким образом:
client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_DataПоле retry_id равно нулю во время первой попытки; в противном случае оно равно auth_key_aux_hash от предыдущей неудачной попытки (см. Пункт 9).
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_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;В другом случае клиент переходит к пункту 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). Если сервер уже забыл ответ или необходимые временные данные, клиенту придется начинать с самого начала.
Сервер может считать, что если клиент уже отправил в следующий запрос, используя данные из предыдущего ответа сервера на конкретного клиента, ответ, как известно, был получен клиентом и может быть забыт сервером.
Пример полного списка запросов, необходимых для генерации ключа авторизации, показан на отдельной странице.