Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate data received via the Telegram's Web App

I'm trying to validate WebApp data but the result is not what I wanted.

Telegram documentation:

data_check_string = ...
secret_key = HMAC_SHA256(<bot_token>, "WebAppData")
if (hex(HMAC_SHA256(data_check_string, secret_key)) == hash) {
    // data is from Telegram
}

MyCode:

BOT_TOKEN = '5139539316:AAGVhDje2A3mB9yA_7l8-TV8xikC7KcudNk'

data_check_string = 'query_id=AAGcqlFKAAAAAJyqUUp6-Y62&user=%7B%22id%22%3A1246866076%2C%22first_name%22%3A%22Dante%22%2C%22last_name%22%3A%22%22%2C%22username%22%3A%22S_User%22%2C%22language_code%22%3A%22en%22%7D&auth_date=1651689536&hash=de7f6b26aadbd667a36d76d91969ecf6ffec70ffaa40b3e98d20555e2406bfbb'
data_check_arr = data_check_string.split('&')
needle = 'hash='
hash_item = ''
telegram_hash = ''
for item in data_check_arr:
    if item[0:len(needle)] == needle:
        telegram_hash = item[len(needle):]
        hash_item = item
data_check_arr.remove(hash_item)
data_check_arr.sort()
data_check_string = "\n".join(data_check_arr)
secret_key = hmac.new("WebAppData".encode(), BOT_TOKEN.encode(),  hashlib.sha256).digest()
calculated_hash = hmac.new(data_check_string.encode(), secret_key, hashlib.sha256).hexdigest()

print(calculated_hash == telegram_hash) # print False

I'm trying to validate webapp data in python, but my code didn't give the intended result. the hash which my code gives me is different from the telegram's one.

UPDATE: valid data added, and bot-token has been changed.

like image 950
BCompl Avatar asked Sep 17 '25 19:09

BCompl


2 Answers

I refactored the code of the previous answers:

  • the function takes hash from given initData

  • it unquotes only values after splitting with & to fix the problem stated in How to validate data received via the Telegram's Web App

  • more pythonic

     def validate_init_data(init_data: str, bot_token: str):
         vals = {k: unquote(v) for k, v in [s.split('=', 1) for s in init_data.split('&')]}
         data_check_string = '\n'.join(f"{k}={v}" for k, v in sorted(vals.items()) if k != 'hash')
    
         secret_key = hmac.new("WebAppData".encode(), bot_token.encode(), hashlib.sha256).digest()
         h = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256)
         return h.hexdigest() == vals['hash']
    

Thanks previous answers for insights!

like image 122
Marat Zaynutdinoff Avatar answered Sep 20 '25 10:09

Marat Zaynutdinoff


You can replace the for-loops with a couple of lines (already incorporates kurdyukovpv's suggestion to unquote the query string):

data_check_string = sorted([ chunk.split("=") for chunk in unquote(data_check_string).split("&") 
        if chunk[:len("hash=")]!="hash="],
        key=lambda x: x[0])
data_check_string = "\n".join([f"{rec[0]}={rec[1]}" for rec in data_check_string])

EDIT: Figured I might as well just post the entire working function I got out of this thread ) :

import hmac
import hashlib
from urllib.parse import unquote

def validate(hash_str, init_data, token, c_str="WebAppData"):
    """
    Validates the data received from the Telegram web app, using the
    method documented here: 
    https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app

    hash_str - the has string passed by the webapp
    init_data - the query string passed by the webapp
    token - Telegram bot's token
    c_str - constant string (default = "WebAppData")
    """

    init_data = sorted([ chunk.split("=") 
          for chunk in unquote(init_data).split("&") 
            if chunk[:len("hash=")]!="hash="],
        key=lambda x: x[0])
    init_data = "\n".join([f"{rec[0]}={rec[1]}" for rec in init_data])

    secret_key = hmac.new(c_str.encode(), token.encode(),
        hashlib.sha256 ).digest()
    data_check = hmac.new( secret_key, init_data.encode(),
        hashlib.sha256)

    return data_check.hexdigest() == hash_str
like image 39
Allaboard Avatar answered Sep 20 '25 10:09

Allaboard