土豆不好吃

Telegram Login Widget 如何验证用户数据

文章目录[显示]

使用Telegram 做OAuth是一件很容易的事情,毕竟整个 Telegram API 都相当开放。

需要注意的一件事情就是,我们需要验证用户的数据确实是来自于 Telegram而不是第三方伪造的。无论是在回调函数还是回调URL中,Telegram都会提供如下参数:
id, first_name, last_name, username, photo_url, auth_date, hash
根据官方文档的说法,验证数据分如下几步:

  1.  把这些字段按照字母顺序排序,组成一个字符串,每一个键值对之间用换行符隔开,如 'auth_date=<auth_date>\nfirst_name=<first_name>\nid=<id>\nusername=<username>'
  2. 计算 bot token 的 sha256
  3. 计算 HMAC_SHA256,明文是上面的这个字符串,密码是bot token 的sha256,然后计算这个结果的hex,比对与他提供的 hash是否相等

看起来很简单是不是,随便丢给 ChatGPT就能出结果。甚至官方还给了个PHP的代码

但是很抱歉的是,这样并不能给保证100%验证成功,甚至可能会完全无法验证。以下是我踩的两个坑

hash字段要排除

在组装字符串的时候,记得排除hash字段,要不然是不可能计算出正确的结果的。

值是空的字段要排除

在 Telegram 中,username、photo_url 等字段不是必须的。在计算时,这种类型的字段如果为空,那么不需要考虑

可怕的是,无论是官方文档,还是PHP示例代码,都没有提到值是空的字段不参与计算的事情。😢

示例代码

Go 的示例代码

type TelegramAuthData struct {
	Id        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Username  string `json:"username"`
	PhotoUrl  string `json:"photo_url"`
	AuthDate  int    `json:"auth_date"`
	Hash      string `json:"hash"`
}

func verifyTelegramAuthData(data TelegramAuthData) bool {
	// Extracting fields and preparing for sorting.
	fields := map[string]string{
		"auth_date":  strconv.Itoa(data.AuthDate),
		"first_name": data.FirstName,
		"last_name":  data.LastName,
		"id":         strconv.Itoa(data.Id),
		"photo_url":  data.PhotoUrl,
		"username":   data.Username,
	}

	// Sorting the fields to create the data-check-string.
	var keys []string
	for key := range fields {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var dataCheckString string
	for _, key := range keys {
		if fields[key] != "" {
			dataCheckString += fmt.Sprintf("%s=%s\n", key, fields[key])
		}
	}
	dataCheckString = dataCheckString[:len(dataCheckString)-1] // Remove the last newline character

	// Calculate the SHA256 hash of the bot's token.
	hasher := sha256.New()
	hasher.Write([]byte(botToken))
	secretKey := hasher.Sum(nil)

	// Compute the HMAC-SHA-256 signature.
	hmacHasher := hmac.New(sha256.New, secretKey)
	hmacHasher.Write([]byte(dataCheckString))
	computedHash := hex.EncodeToString(hmacHasher.Sum(nil))

	// Compare the computed HMAC with the received hash.
	return computedHash == data.Hash
}

Python 版本

def verify_telegram_authdata(data: dict) -> bool:
    # Extracting fields and preparing for sorting
    fields = {
        "auth_date": data.get("auth_date"),
        "first_name": data.get("first_name"),
        "last_name": data.get("last_name"),
        "id": data.get("id"),
        "photo_url": data.get("photo_url"),
        "username": data.get("username"),
    }

    # Sorting the fields to create the data-check-string
    keys = sorted(fields)
    data_check_string = "\n".join(f"{key}={fields[key]}" for key in keys if fields[key])

    # Calculate the SHA256 hash of the bot's token
    hasher = hashlib.sha256()
    # bot token token
    hasher.update("token".encode("utf-8"))
    secret_key = hasher.digest()

    # Compute the HMAC-SHA-256 signature
    hmac_hasher = hmac.new(secret_key, data_check_string.encode("utf-8"), hashlib.sha256)
    computed_hash = hmac_hasher.hexdigest()

    # Compare the computed HMAC with the received hash
    return computed_hash == data.get("hash")


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/telegram-login-widget-verify-data.html
退出移动版