Проверка подлинности Telegram WebAppData на C#

TelegramWebAppData NuGet

При разработке Телеграм ботов на .NET, и в частности мини приложений, возникает необходимость проверить, действительно ли данные получены от Телеграм, а не отправлены кем-то. Для решения этой задачи обычно используется JavaScript, но мне, как разработчику на C# было интересно реализовать такой функционал именно силами C#. Сразу отмечу, что не всё можно сделать только силами шарпа, но минимализировать js код все же можно. О разработке ботов и мини приложений на .NET как-нибудь отдельно расскажу, а пока расскажу именно про валидацию данных.

Итак, опуская тему получения данных от Телеграм, мы имеем строку вида:
query_id=AAFOQRZEAAAAAE5BFkSIO04K&user={"id":1142309198,"first_name":"Alex","last_name":"G.M.","username":"Alexey_G_M","language_code":"ru","is_premium":true,"allows_write_to_pm":true}&auth_date=1730552020&hash=b53605c8fbc96630cfc0ece99ff328f33d84898bd143ab3ad31ac7330d60b495

Задача: проверить, принадлежат ли данные из этой строки действительно пользователю, чьи данные там отображены и действительно ли он эти данные отправил из моего бота. В документации Телеграм этот момент очень слабо описан и не сразу понятно, что именно от нас хотят:

Проверка подлинности Telegram WebAppData на C#

Все вроде понятно, но не очевидно. Что ж, пришлось искать пути решения данной задачи. И готового решения я не нашел, поэтому пришлось писать своё решение, а заодно и сделать библиотеку, на случай, если кому-то так же понадобится подобное реализовать в своем решении.

Если нужно готовое решение:

Либо собрать библиотеку из исходников на GitHub: https://github.com/algmironov/TelegramMiniAppHashChecker

А теперь расскажу, как библиотека реализована.

C#
public static bool Validate(string initData, string token, string cStr = "WebAppData")
{
	try
	{
		var (dataCheckString, hash) = ParseAuthString(initData);

		// Создаем секретный ключ
		var secretKey = EncodeHmac(token, Encoding.UTF8.GetBytes(cStr));

		// Создаем ключ валидации
		var validationKeyBytes = EncodeHmac(dataCheckString, secretKey);
		var validationKey = BitConverter.ToString(validationKeyBytes).Replace("-", "").ToLower();

		if (validationKey == hash)
		{
			return true;
		}
	}
	catch (Exception)
	{
		return false;
	}

	return false;
}

Один единственный метод Validate принимает строку, полученную от Телеграм, токен бота (того, который мы разрабатываем) и, на всякий случай, может принимать строку для хэширования. По-умолчанию используется "WebAppData".

Оставил возможность на случай, если Телеграм что-то поменяет в api и будет использовать другую строку. Вряд ли, но..

Итак, второй метод в классе, выполняющий парсинг данных из строки:

C#
private static (string dataCheckString, string? hash) ParseAuthString(string initData)
{
	try
	{
		var query = HttpUtility.ParseQueryString(initData);
		var hash = query["hash"];
		query.Remove("hash");

		// Сортируем параметры
		var parameters = query.AllKeys
			.OrderBy(k => k)
			.Select(k => $"{k}={query[k]}")
			.ToList();

		var dataCheckString = string.Join("\n", parameters);

		return (dataCheckString, hash);
	}
	catch (Exception)
	{
		throw;
	}
}

Здесь мы отделяем hash от строки, он понадобится позднее. Далее вся строка разбивается на пары ключ-значение и соединяются в одну строку с разделителем в виде новой строки. Далее метод возвращает кортеж (Tuple).

В следующем методе происходит хэширование:

C#
private static byte[] EncodeHmac(string message, byte[] key)
{
   using var hmac = new HMACSHA256(key);
   return hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
}

Тут все просто: на вход подается строка и ключ, которым хэшируется строка. Последовательно выполняются хэширование ключа и затем хэширование остальных данных.

В самом конце сравнивается полученных хэш с хэшем из изначально полученной от Телеграм строки.

В целом, ничего сложного. Правда пришел я к этому не сразу, пришлось помучаться.

Надеюсь, кому-то статья будет полезна, а может и Nuget пакет.

Оцените статью
Добавить комментарий

  1. yazdorova.com

    Эта тема давно меня интересует.
    Спасибо, что подняли её, будет интересно пообщаться!

    Ответить