Почему Telegram Passport — никакой не End to End, а обычный Man-In-The-Middle

в 7:00, , рубрики: end-to-end, facepalm, passport, password security, telegram, информационная безопасность, криптография, Социальные сети и сообщества

Привет, %username%!

Почему Telegram Passport — никакой не End to End, а обычный Man-In-The-Middle - 1

В обсуждении новости про Passport разгорелись жаркие дискуссии на тему безопасности последней поделки от авторов Telegram.

Давайте посмотрим, как он шифрует ваши персональные данные и поговорим о настоящем End-To-End. А между делом придумаем MITM атаку на протокол Passport.

В двух словах, как работает Passport.

  • Вы локально с помощью пароля шифруете свои персональные данные (имя, email, скан паспорта, другие документы).
  • Зашифрованные данные + метаинформация загружаются в облако Telegram.
  • Когда нужно авторизоваться на сервисе, клиент скачивает данные из облака, расшифровывает их паролем, перешифровывает на публичный ключ того сервиса, который запросил информацию, и отправляет.

Мы рассмотрим первую часть, которая касается шифрования и хранения персональных данных.

End to End по мнению разработчиков заключается в том, что облако Telegram якобы не может расшифровать ваши персональные данные, а видит только «случайный шум».

Давайте подробнее глянем на код алгоритма шифрования персональных данных из десктоп клиента, который находится тут и посмотрим, удовлетворяет ли результат его работы критериям End-To-End.

Всё начинается с пароля. Вот место, где он превращается в промежуточный ключ шифрования.

bytes::vector CountPasswordHashForSecret(
		bytes::const_span salt,
		bytes::const_span password) {
	return openssl::Sha512(bytes::concatenate(
		salt,
		password,
		salt));
}

Тут берётся случайная соль, дважды конкатенируется с паролем и прогоняется через хэш SHA-512. На первый взгляд ничего необычного. Но!

На дворе 2018 год. На одном хорошем GPU можно перебирать примерно полтора миллиарда SHA-512 в секунду. 10 GPU переберут все возможные сочетания 8-значных паролей из 94х символьного словаря (англ буквы, цифры, спец символы) меньше чем за 5 дней.

Давным давно существуют способы усложнить жизнь тем, кто перебирает пароли на GPU, но разработчики Telegram решили не утруждать себя их внедрением.

Дальше. Хэшем из пароля шифруется еще один почти случайный ключ, который генерируется так:

bytes::vector GenerateSecretBytes() {
	auto result = bytes::vector(kSecretSize);
	memset_rand(result.data(), result.size());
	const auto full = ranges::accumulate(
		result,
		0ULL,
		[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
	const auto mod = (full % 255ULL);
	const auto add = 255ULL + 239 - mod;
	auto first = (static_cast<uchar>(result[0]) + add) % 255ULL;
	result[0] = static_cast<gsl::byte>(first);
	return result;
}

и используется используется для шифрования данных вместе с еще одной штукой, о которой ниже.

Случайный он «почти», потому что разработчики телеграма никогда не слышали о HMAC и AEAD и вместо того, чтобы использовать нормальные средства для проверки корректности расшифровки, они делают так, чтобы остаток от деления суммы байт ключа был равен 239, что при расшифровке и проверяют:

bool CheckBytesMod255(bytes::const_span bytes) {
	const auto full = ranges::accumulate(
		bytes,
		0ULL,
		[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
	const auto mod = (full % 255ULL);
	return (mod == 239);
}

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

Идём дальше. Непосредственно метод, шифрующий данные. Тут много букв, поэтому по кускам:

EncryptedData EncryptData(
		bytes::const_span bytes,
		bytes::const_span dataSecret) {
	constexpr auto kFromPadding = kMinPadding + kAlignTo - 1;
	constexpr auto kPaddingDelta = kMaxPadding - kFromPadding;
	const auto randomPadding = kFromPadding
		+ (rand_value<uint32>() % kPaddingDelta);
	const auto padding = randomPadding
		- ((bytes.size() + randomPadding) % kAlignTo);
	Assert(padding >= kMinPadding && padding <= kMaxPadding);

	auto unencrypted = bytes::vector(padding + bytes.size());
	Assert(unencrypted.size() % kAlignTo == 0);

	unencrypted[0] = static_cast<gsl::byte>(padding);
	memset_rand(unencrypted.data() + 1, padding - 1);
	bytes::copy(
		gsl::make_span(unencrypted).subspan(padding),
		bytes);

Тут к данным дописываются от 32 до 255 случайных байт. Делается это чтобы разнообразить
переменную dataHash. Это хэш от незашифрованных данных, смешанных со случайными байтами.

	const auto dataHash = openssl::Sha256(unencrypted);
	const auto bytesForEncryptionKey = bytes::concatenate(
		dataSecret,
		dataHash);

	auto params = PrepareAesParams(bytesForEncryptionKey);
	return {
		{ dataSecret.begin(), dataSecret.end() },
		{ dataHash.begin(), dataHash.end() },
		Encrypt(unencrypted, std::move(params))
	};
}

Тут формируется ключ шифрования персональных данных. Он получается с помощью еще одного вызова SHA-512 от сгенерированного выше почти случайного ключа, сконкатеннированного с dataHash.

Итог

Почему Telegram Passport — никакой не End to End, а обычный Man-In-The-Middle - 2В облако передаются:

  1. Хэш от персональных данных, смешанных со случайными байтами
  2. Зашифрованный паролем почти случайный ключ
  3. Соль
  4. Зашифрованные данные

Это далеко не «случайный шум», тут есть всё необходимое, включая ключ шифрования, защищенный паролем. И это позволяет добраться до данных пользователей гораздо, гораздо быстрее чем перебирать все возможные комбинации ключей AES (2^256).

Так же большому сомнению подвергаются такие изобретённые авторами Telegram механизмы как проверка ключа на валидность с помощью суммы байт, участие самих данных в формировании ключа их же шифрования и хэш от данных вместо HMAC.

Примерный алгоритм брутфорса:

  1. Берем пароль по порядку, генерируем хэш от него и соли (GPU)
  2. Пробуем расшифровать ключ (AES-NI)
  3. Смотрим на сумму байт и сразу отсеиваем почти все неверные пароли.
  4. Формируем ключ-кандидат на расшифровывание данных с помощью еще одного вызова SHA-512 (GPU)
  5. Пробуем расшифровать первый блок данных (AES-NI)
  6. Чтобы не тратить время на полное расшифровывание и еще один SHA-256, мы можем ускорить брутфорс, проверяя первый байт выравнивания так же как они сами это делают:

if (padding < kMinPadding
		|| padding > kMaxPadding
		|| padding > decrypted.size()) {

и первый байт расшифрованного текста, который из-за использования JSON будет всегда "{" или "[".

Итак, мы видим, что шифрование персональных данных критически зависит от сложности пароля. Все этапы перебора отлично ускоряются аппаратно. Либо с помощью GPU, либо с помощью инструкций AES-NI. Конечно, можно установить длинный, безопасный пароль и надеяться что прокатит. Но как вы сами думаете, какой процент из двухсот миллионов пользователей телеграма будет делать пароли длиннее восьми символов?

Добавьте к этому сомнительные техники генерации ключей и проверки валидности расшифровываемой информации, которые не используют стандартные проверенные механизмы, а прямо нарушают принцип Don't roll your own crypto и станет ясно, что это не End-to-End, а сколоченная на коленке поделка от которой неприятно пахнет.

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

Настоящий End-to-End

E2E называется так, потому то позволяет показывать третьим лицам зашифрованные данные не опасаясь за их сохранность. Как мы увидели, это условие новым продуктом телеграма не выполняется.

Но, к примеру, если правильно зашифровать данные не на хэш от пароля, а на публичный ключ, то никакой даже миллиардный кластер не сможет к ним и близко подобраться. Взгляните на Signal, другие мессенджеры на его основе (WhatsApp, etc). Весь мир уже давно и успешно использует современную асимметричную криптографию, алгоритмы, мешающие перебору паролей, стойкие стандартные криптографические конструкции.

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

Но Telegram пошел своим, особым путём переизобретения криптопримитивов и ослабления защиты. Ну а что, денег им отвалили, за последствия можно не беспокоиться.

P.S. если есть желание посмотреть как работает чат с настоящим E2E, у VirgilSecurity есть демо проект, который можно скачать и поиграться.

Bonus! сценарий атаки MITM на вторую часть протокола:

  • Вы авторизуетесь на сервисе «А»
  • Бот сервиса «А» отправляет запрос на авторизацию в облако телеграма
  • Облако телеграма переправляет этот запрос вам...
  • А вот и нет! Телеграм чаты c ботами не End-to-End, бебебе
  • Телеграм сервис перехватывает запрос на авторизацию и подсовывает сервису любые другие данные, например террористов
  • Или подменяет публичный ключ бота на свой (а что, подписи нет, вы никак не проверите) и считывает ваши персональные данные даже без мороки с паролями
  • Вы в заднице, т.к. без цифровой подписи ничего не сможете доказать
  • The end. To end-to-end.

Автор: Scratch

Источник