При разработке Телеграм ботов на .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
Задача: проверить, принадлежат ли данные из этой строки действительно пользователю, чьи данные там отображены и действительно ли он эти данные отправил из моего бота. В документации Телеграм этот момент очень слабо описан и не сразу понятно, что именно от нас хотят:

Все вроде понятно, но не очевидно. Что ж, пришлось искать пути решения данной задачи. И готового решения я не нашел, поэтому пришлось писать своё решение, а заодно и сделать библиотеку, на случай, если кому-то так же понадобится подобное реализовать в своем решении.
Если нужно готовое решение:
NuGet\Install-Package TelegramWebAppDataValidator -Version 1.0.0
Либо собрать библиотеку из исходников на GitHub: https://github.com/algmironov/TelegramMiniAppHashChecker
А теперь расскажу, как библиотека реализована.
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 и будет использовать другую строку. Вряд ли, но..
Итак, второй метод в классе, выполняющий парсинг данных из строки:
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).
В следующем методе происходит хэширование:
private static byte[] EncodeHmac(string message, byte[] key)
{
using var hmac = new HMACSHA256(key);
return hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
}
Тут все просто: на вход подается строка и ключ, которым хэшируется строка. Последовательно выполняются хэширование ключа и затем хэширование остальных данных.
В самом конце сравнивается полученных хэш с хэшем из изначально полученной от Телеграм строки.
В целом, ничего сложного. Правда пришел я к этому не сразу, пришлось помучаться.
Надеюсь, кому-то статья будет полезна, а может и Nuget пакет.
Эта тема давно меня интересует.
Спасибо, что подняли её, будет интересно пообщаться!