Защищенный чат — непубличный чат, общение в котором ведется с помощью сообщений, зашифрованных ключом, который есть только у его участников. Из определения следует, что никакие третьи лица, не смогут получить доступ к расшифрованному содержимому без доступа к одному из устройств.
Новая сущность, появившаяся в восьмом слое API.
При генерации ключа используется протокол Диффи-Хеллмана. Подробнее см. Wikipedia
Рассмотрим ситуацию: пользователь A хочет начать защищенное общение с пользователем B.
Пользователь A выполняет метод messages.getDhConfig для получения конфигурационных параметров метода Диффи-Хеллмана: prime, primitive root.
Важно выполнять этот метод перед каждой новой процедурой генерации ключа. Имеет смысл кешировать значения параметров вместе с версией, чтобы не переполучать значения целиком каждый раз. Если версия, хранимая клиентом, по-прежнему актуальна, сервер вернет конструктор messages.dhConfigNotModified.
В случае, если на клиенте недостаточно хороший генератор случайных чисел, имеет смысл передавать параметр random_length > 0, чтобы сервер сгенерировал свою случайную последовательность random соответствующей длины.
Важно: использовать серверную случайную последовательность в чистом виде может быть небезопасно. Обязательно необходимо подмешивать клиентскую, например, сгененерировать своё случайное число той же длины client_random и использовать final_random := random XOR client_random
Клиент A вычисляет случайное 2048-битное число a (используя достаточно много энтропии или серверный random, см. выше), и выполняет метод messages.requestEncryption, передав туда g_a := pow(g, a) mod dh_prime
.
Пользователю B на все привязанные авторизационные ключи (все авторизованные устройства) приходит обновление updateEncryption с конструктором чата encryptedChatRequested. Необходимо отобразить пользователю базовую информацию о пользователе A и предложить одобрить или отклонить запрос.
После того, как пользователь B в интерфейсе клиента подтверждает создание защищенного чата с A, клиент B также получает актуальные конфигурационные параметры метода Диффи-Хеллмана. После этого он генерирует случайное 2048-битное число b по правилам, аналогичным a.
Имея g_a, nonce полученные из обновления с encryptedChatRequested, он может сразу сгенерировать итоговый общий ключ: key = (pow(g_a, b) mod dh_prime) xor nonce
.
Его отпечаток key_fingerprint равен младшим 64 битам SHA1 (key).
Клиент B выполняет метод messages.acceptEncryption, передав туда g_b := pow(g, b) mod dh_prime
и key_fingerprint.
На все авторизованные устройства клиента B, кроме текущего, будут отправлены обновления updateEncryption с конструктором encryptedChatDiscarded. После этого с этим зашифрованным чатом сможет работать только устройство B, с которого был совершен вызов messages.acceptEncryption.
Пользователю A на авторизационный ключ, с которого был инициирован чат, приходит обновление updateEncryption с конструктором encryptedChat.
Клиент A, имея g_b, nonce из обновления, может также получить общий ключ key = (pow(g_b, a) mod dh_prime) xor nonce
. Если отпечаток полученного ключа совпадает с переданным в encryptedChat, можно начать отправку и обработку входящих сообщений. В противном случае обязательно необходимо выполнить messages.discardEncryption и оповестить пользователя.
Формируется TL-объект типа DecryptedMessage, содержащий сообщение в открытом виде. Для обратной совместимости объект необходимо оборачивать в конструктор decryptedMessageLayer с указанием поддерживаемого слоя (начиная с 8).
Полученная конструкция сериализуется в массив байтов по общим правилам TL. В начало полученного массива дописывается 4 байта длины массива, не включая эти 4 байта.
Вычисляется ключ сообщения msg_key, как младшие 128 бит от SHA1 полученных на предыдущем шаге данных.
Массив байт дополняется случайными данными до кратности длины 16 байта.
Вычисляется AES ключ и инициализационный вектор ( key — общий ключ, полученный на этапе Генерация ключа, x = 0 ):
Данные шифруются 128-битным ключом aes_key и 128-битным инициализионным вектором aes_iv, посредством шифра AES-256 в сцепленном режиме (IGE). В начало полученного массива байт дописывается ключ сообщения msg_key.
В обратном порядке выполняются действия, приведенные выше.
При получении зашифрованного сообщения надо обязательно проверить, что msg_key действительно равен младшим 128 битам SHA1 от расшифрованного сообщения.
В случае, если слой сообщения больше поддерживаемого клиентом, необходимо сообщить пользователю об устаревшей версии клиента и предложить обновиться.
Все файлы, отправляемые в защищенные чаты шифруются одноразовыми ключами, никак не связанными с общим ключом чата. Под отправкой зашифрованного файла подразумевается прикрепление адреса зашифрованного файла снаружи к зашифрованному сообщению, с помощью параметра file метода messages.sendEncryptedFile, а также передача ключа для непосредственной расшифровки внутри тела сообщения (параметр key в конструкторах decryptedMessageMediaPhoto, decryptedMessageMediaVideo и decryptedMessageMediaFile.
Перед началом отправки файла в защищенный чат вычисляется 2 случайных 256-битных числа, которые будут являться AES ключом и инициализационным вектором, с помощью которых файл шифруется. Аналогично, используется шифр AES-256 в сцепленном режиме (IGE).
Отпечаток ключа вычисляется следующим образом:
Зашифрованное содержимое файла сохраняется на сервере аналогично незашифрованному, кусками с помощью вызовов upload.saveFilePart.
Последующий вызов messages.sendEncryptedFile присвоит сохраненному файлу идентификатор и отправит адрес вместе с сообщением. Получателю придет обновление с encryptedMessage, в параметр file содержит информацию о файле.
Полученные и отправленные зашифрованные файлы можно пересылать в другие защищенные чаты с помощью конструктора inputEncryptedFile, чтобы дважды не сохранять одно и то же содержимое на сервер.
Защищенные чаты привязаны не к пользователям, а к конкретным устройствам (а точней, к авторизационным ключам). Обычный ящик сообщений, который использует pts, как описание состояния клиента, не годится, поскольку предзначен для длительного хранения сообщений и доступа к ним с разных устройств.
Для решения проблемы вводится дополнительная временная очередь обновлений. При отправке обновления о сообщении из защищенного чата, передается новое значение qts, который позволяет восстановить разницу при длительном отсутствии соединения или потере обновления.
С увеличением количества событий, значение qts монотонно возрастает (не всегда на 1). Начальное значение может (и будет) не равняться 0.
Факт получения и сохранения событий из временной очереди клиентом подтверждается явно вызовом метода messages.receivedQueue или неявно запросом updates.getDifference (значение переданного qts, а не итовое состояние). Все сообщения, факт доставки которых клиент подтвердил, а также сообщения старше 7 дней, могут быть (и будут) удалены с сервера.
При разавторизации очередь событий соответствующего устройства будет принудительно очищена, значение qts станет неактуальным.