文章目录 [显示]
使用 Telegram 做 OAuth 是一件很容易的事情,毕竟整个 Telegram API 都相当开放。
需要注意的一件事情就是,我们需要验证用户的数据确实是来自于 Telegram 而不是第三方伪造的。无论是在回调函数还是回调 URL 中,Telegram 都会提供如下参数:
id, first_name, last_name, username, photo_url, auth_date, hash
根据官方文档的说法,验证数据分如下几步:
- 把这些字段按照字母顺序排序,组成一个字符串,每一个键值对之间用换行符隔开,如
'auth_date=<auth_date>\nfirst_name=<first_name>\nid=<id>\nusername=<username>'
- 计算 bot token 的 sha256
- 计算 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")