2462 字
12 分钟
QQ 扫码登录逆向分析,从抓包到算法还原
研究了一个周末,终于搞明白腾讯是怎么做扫码登录的了
2025-12-29
-
-

一、研究背景#

1.1 为啥要研究这个#

主要是好奇吧,每天都在用 QQ,但是从来没想过扫码登录背后是怎么实现的。而且网上关于这块的资料要么太老了,要么讲得不清楚,干脆自己抓包分析一下。

1.2 分析工具#

  • Chrome 开发者工具(F12 大法好)
  • Fiddler 抓包
  • Python + requests 验证

二、QQ 扫码登录整体流程#

先上一张整体流程图,有个大概印象:

┌──────────────────────────────────────────────────────────────────┐
│ QQ 扫码登录完整流程 │
└──────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ 开始登录 │
└──────┬──────┘
v
┌─────────────────────────────────────┐
│ Step 1: 访问 xlogin 页面 │
│ URL: xui.ptlogin2.qq.com/xlogin │
│ 目的: 获取 pt_login_sig Cookie │
└──────────────────┬──────────────────┘
v
┌─────────────────────────────────────┐
│ Step 2: 请求二维码 │
│ URL: ssl.ptlogin2.qq.com/ptqrshow │
│ 返回: 二维码图片 + qrsig Cookie │
└──────────────────┬──────────────────┘
v
┌─────────────────────────────────────┐
│ Step 3: 计算 ptqrtoken │
│ 算法: hash(qrsig) │
│ 用于: 后续轮询请求的签名 │
└──────────────────┬──────────────────┘
v
┌─────────────────────────────────────┐
│ Step 4: 轮询扫码状态 │
│ URL: ssl.ptlogin2.qq.com/ptqrlogin │
│ 返回: 状态码 + 跳转URL │
└──────────────────┬──────────────────┘
┌───────────┼───────────┐
│ │ │
v v v
┌───────┐ ┌────────┐ ┌────────┐
│ 成功 │ │ 等待中 │ │ 已失效 │
│ (0) │ │(65/66) │ │(10009) │
└───┬───┘ └────┬───┘ └────────┘
│ │
│ └──> 继续轮询
v
┌─────────────────────────────────────┐
│ Step 5: 访问跳转 URL │
│ 目的: 获取 skey, p_skey 等 Cookie │
└──────────────────┬──────────────────┘
v
┌─────────────────────────────────────┐
│ Step 6: 计算 g_tk (bkn) │
│ 算法: hash(skey) │
│ 用于: 后续业务接口的鉴权 │
└──────────────────┬──────────────────┘
v
┌─────────────┐
│ 登录完成 │
└─────────────┘

整个流程看起来不复杂,但是每一步都有坑,下面一个一个说。


三、Step 1:初始化登录环境#

3.1 请求分析#

首先要访问腾讯的 xlogin 页面:

GET https://xui.ptlogin2.qq.com/cgi-bin/xlogin

请求参数:

参数示例值说明
appid715030901应用ID,不同业务不一样
daid73域ID,和 appid 配套
s8固定值
pt_3rd_aid0第三方应用ID

3.2 appid 对照表#

不同的 QQ 业务有不同的 appid,这个是抓包抓出来的:

业务appiddaid
QQ群管理71503090173
QQ空间5490009125
QQ邮箱5220057054
腾讯文档1006102461

用错 appid 会导致后面拿到的 Cookie 在对应业务上用不了,这个坑我踩过。

3.3 响应分析#

这个请求主要是为了在 Cookie 里设置 pt_login_sig,后面轮询的时候要用。

Set-Cookie: pt_login_sig=xxxxxx; Domain=.qq.com; Path=/

四、Step 2:获取二维码#

4.1 请求分析#

GET https://ssl.ptlogin2.qq.com/ptqrshow

请求参数:

参数示例值说明
appid715030901应用ID
e2二维码类型
lM二维码大小(M=中等)
s3样式
d72边距像素
v4版本
t1703123456.789时间戳,防缓存
daid73域ID
pt_3rd_aid0第三方应用ID

4.2 响应分析#

响应体是二维码的 PNG 图片数据,同时在 Cookie 里设置 qrsig

Set-Cookie: qrsig=xxxxxx; Domain=.qq.com; Path=/

这个 qrsig 非常重要,是后面计算 ptqrtoken 的关键。

4.3 二维码内容#

扫描二维码会得到一个 URL,格式大概是:

https://ssl.ptlogin2.qq.com/ptqrlogin?xxx

手机 QQ 扫描后会访问这个 URL 完成授权。


五、Step 3:ptqrtoken 算法分析#

5.1 算法来源#

这个算法是从腾讯的 JS 代码里扒出来的。打开浏览器开发者工具,在 Sources 面板搜索 ptqrtoken,能找到这段代码:

function getptqrtoken(qrsig) {
var e = 0;
for (var i = 0; i < qrsig.length; i++) {
e += (e << 5) + qrsig.charCodeAt(i);
}
return e & 2147483647;
}

5.2 算法解析#

用 Python 实现就是:

def get_ptqrtoken(qrsig: str) -> int:
hash_val = 0
for char in qrsig:
hash_val += (hash_val << 5) + ord(char)
return hash_val & 2147483647

逐步分析:

  1. 初始化 hash_val = 0
  2. 遍历 qrsig 的每个字符
  3. hash_val << 5 等价于 hash_val * 32
  4. 加上当前字符的 ASCII 码值
  5. 最后和 0x7FFFFFFF(2147483647)做与运算

5.3 为什么要做与运算#

& 2147483647 的作用是:

  1. 保证结果是正整数(去掉符号位)
  2. 限制结果在 32 位整数范围内
  3. 防止溢出

5.4 计算示例#

假设 qrsig = “abc”:

初始: hash_val = 0
第1轮 (char = 'a', ASCII = 97):
hash_val = 0 + (0 << 5) + 97 = 97
第2轮 (char = 'b', ASCII = 98):
hash_val = 97 + (97 << 5) + 98 = 97 + 3104 + 98 = 3299
第3轮 (char = 'c', ASCII = 99):
hash_val = 3299 + (3299 << 5) + 99 = 3299 + 105568 + 99 = 108966
最终: 108966 & 2147483647 = 108966

六、Step 4:轮询扫码状态#

6.1 请求分析#

GET https://ssl.ptlogin2.qq.com/ptqrlogin

这个接口参数巨多,我整理了一下:

参数示例值说明
u1https://qun.qq.com/member.html登录成功后跳转地址
ptqrtoken123456789上一步算出来的
ptredirect0固定值
h1固定值
t1固定值
g1固定值
from_ui1固定值
ptlang2052语言代码,2052=简体中文
action0-0-1703123456789格式: 0-0-毫秒时间戳
js_ver24051615JS版本号,会变
js_type1固定值
login_sigxxx从 Cookie 获取
pt_uistyle40UI样式
aid715030901appid
daid73域ID

6.2 响应格式#

响应是 JSONP 格式:

ptuiCB('状态码','0','跳转URL','0','提示信息','昵称')

6.3 状态码对照表#

状态码含义处理方式
0登录成功提取跳转URL,进入下一步
65已扫描,待确认继续轮询
66二维码未失效继续轮询
67等待扫描继续轮询
10009二维码已失效重新获取二维码
10006二维码已失效重新获取二维码

6.4 响应示例#

等待扫描:

ptuiCB('67','0','','0','二维码未失效。','')

已扫描待确认:

ptuiCB('65','0','','0','二维码已扫描,请在手机上确认登录。','张三')

登录成功:

ptuiCB('0','0','https://ssl.ptlogin2.qq.com/check_sig?pttype=1&uin=123456789&service=...','0','登录成功!','张三')

6.5 提取跳转 URL#

登录成功后需要用正则提取跳转 URL:

import re
match = re.search(r"ptuiCB\('0','0','([^']+)','0'", response_text)
if match:
redirect_url = match.group(1)

7.1 请求分析#

访问上一步拿到的跳转 URL:

GET https://ssl.ptlogin2.qq.com/check_sig?pttype=1&uin=xxx&service=xxx...

这个请求会经过多次 302 重定向。

7.2 重定向链路#

ssl.ptlogin2.qq.com/check_sig
└──> ptlogin2.qun.qq.com/check_sig_v3
└──> qun.qq.com/member.html (最终页面)

在重定向过程中,会设置以下关键 Cookie:

Cookie说明
skey.qq.com最重要,算 g_tk 要用
p_skey.qun.qq.com某些接口需要
pt4_token.qq.com某些接口需要
uin.qq.com用户QQ号

7.4 注意事项#

  1. 必须设置正确的 Referer:https://ssl.ptlogin2.qq.com/
  2. 要允许自动重定向(allow_redirects=True
  3. 要用 Session 保持 Cookie

八、Step 6:g_tk (bkn) 算法分析#

8.1 算法来源#

同样是从腾讯 JS 里扒的,搜索 getACSRFTokeng_tk

function getACSRFToken(skey) {
var hash = 5381;
for (var i = 0; i < skey.length; i++) {
hash += (hash << 5) + skey.charCodeAt(i);
}
return hash & 2147483647;
}

8.2 算法解析#

Python 实现:

def get_g_tk(skey: str) -> int:
hash_val = 5381
for char in skey:
hash_val += (hash_val << 5) + ord(char)
return hash_val & 2147483647

8.3 和 ptqrtoken 的区别#

ptqrtokeng_tk
输入qrsigskey
初始值05381
算法相同相同

唯一的区别就是初始值,ptqrtoken 是 0,g_tk 是 5381。

8.4 为什么是 5381#

5381 是 DJB2 哈希算法的魔数,这个算法是 Daniel J. Bernstein 发明的。选择 5381 的原因据说是:

  1. 5381 是质数
  2. 实验证明这个值的哈希分布效果好

反正腾讯用的就是这个,别问为什么,问就是玄学。

8.5 计算示例#

假设 skey = “@abc”:

初始: hash_val = 5381
第1轮 (char = '@', ASCII = 64):
hash_val = 5381 + (5381 << 5) + 64
= 5381 + 172192 + 64
= 177637
第2轮 (char = 'a', ASCII = 97):
hash_val = 177637 + (177637 << 5) + 97
= 177637 + 5684384 + 97
= 5862118
... 以此类推

九、完整时序图#

┌────────┐ ┌────────────────┐ ┌────────────────┐
│ Client │ │ QQ Login API │ │ QQ Mobile │
└───┬────┘ └───────┬────────┘ └───────┬────────┘
│ │ │
│ GET /xlogin │ │
│──────────────────────>│ │
│ │ │
│ Set-Cookie: │ │
│ pt_login_sig │ │
│<──────────────────────│ │
│ │ │
│ GET /ptqrshow │ │
│──────────────────────>│ │
│ │ │
│ 二维码图片 + │ │
│ Set-Cookie: qrsig │ │
│<──────────────────────│ │
│ │ │
│ 计算 ptqrtoken │ │
│ = hash(qrsig) │ │
│ │ │
│ GET /ptqrlogin │ │
│ (轮询) │ │
│──────────────────────>│ │
│ │ │
│ ptuiCB('67'...) │ │
│ 等待扫描 │ │
│<──────────────────────│ │
│ │ │
│ │ 用户扫码 │
│ │<──────────────────────────│
│ │ │
│ GET /ptqrlogin │ │
│──────────────────────>│ │
│ │ │
│ ptuiCB('65'...) │ │
│ 已扫描待确认 │ │
│<──────────────────────│ │
│ │ │
│ │ 用户确认 │
│ │<──────────────────────────│
│ │ │
│ GET /ptqrlogin │ │
│──────────────────────>│ │
│ │ │
│ ptuiCB('0'...) │ │
│ 登录成功 + 跳转URL │ │
│<──────────────────────│ │
│ │ │
│ GET /check_sig │ │
│ (跳转URL) │ │
│──────────────────────>│ │
│ │ │
│ 302 重定向 │ │
│ Set-Cookie: │ │
│ skey, p_skey... │ │
│<──────────────────────│ │
│ │ │
│ 计算 g_tk │ │
│ = hash(skey) │ │
│ │ │
│ 登录完成,可调用 │ │
│ 业务接口 │ │
│ │ │

十、踩坑记录#

10.1 js_ver 过期#

腾讯会不定期更新 JS 版本号,如果发现登录突然失败,可以试试更新 js_ver 参数。

获取方法:打开 QQ 登录页面,在 Network 里找 ptqrlogin 请求,看它带的 js_ver 是多少。

10.2 Referer 检查#

腾讯对 Referer 检查很严格,每个请求都要设置正确的 Referer,不然会返回错误。

请求Referer
ptqrshowhttps://xui.ptlogin2.qq.com/
ptqrloginhttps://xui.ptlogin2.qq.com/
check_sighttps://ssl.ptlogin2.qq.com/

必须用 Session 保持 Cookie,不能每次请求都新建连接。

# 错误做法
requests.get(url1)
requests.get(url2) # Cookie 丢了
# 正确做法
session = requests.Session()
session.get(url1)
session.get(url2) # Cookie 自动带上

10.4 时间戳精度#

action 参数要用毫秒级时间戳:

# 错误
action = f"0-0-{int(time.time())}" # 秒级,会失败
# 正确
action = f"0-0-{int(time.time() * 1000)}" # 毫秒级

10.5 User-Agent#

用默认的 Python UA 可能会被拦截,建议伪装成浏览器:

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."
}

十一、总结#

QQ 扫码登录的核心就两个算法:

  1. ptqrtoken:用 qrsig 算,初始值 0
  2. g_tk/bkn:用 skey 算,初始值 5381

两个算法本质上是一样的,都是 DJB2 哈希的变种,只是初始值不同。

整个流程说白了就是:

  1. 拿二维码和 qrsig
  2. 算 ptqrtoken,轮询等用户扫码
  3. 扫码成功拿跳转 URL
  4. 访问跳转 URL 拿 skey
  5. 用 skey 算 g_tk,完事儿

腾讯的接口没有文档,全靠抓包分析,而且随时可能更新。如果哪天突然不能用了,大概率是腾讯又改接口了,重新抓包分析吧。


研究这玩意花了我一个周末,期间无数次想砸电脑。

不过搞明白之后还是挺有成就感的,至少知道了每天用的 QQ 背后是怎么运作的。

希望这篇分析能帮到同样在研究这块的兄弟们,少走点弯路。

就这样,我继续去听歌摸鱼了。

这篇文章是否对你有帮助?

发现错误或想要改进这篇文章?

在 GitHub 上编辑此页
QQ 扫码登录逆向分析,从抓包到算法还原
作者
lansonsam
发布于
2025-12-29
许可协议
CC BY-NC-SA 4.0