使用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")



