type
Post
status
Published
date
Jul 13, 2026
slug
summary
调试 Akamai 站点爬虫时发现,Requests 默认发包极易被风控识别拦截,单纯修改请求头完全无法规避。
为搞懂底层识别逻辑,本文从零梳理 TLS 握手、JA3/JA4 指纹核心原理,搭配 Wireshark 抓包对比脚本与浏览器的数据包差异,拆解指纹识别完整链路,最后给出复刻浏览器 TLS 特征、自建请求库的实操方案。
tags
TLS
category
转载
icon
password
网址
上次编辑时间
Jul 13, 2026 05:20 AM
comment
AI 总结
调试 Akamai 站点爬虫时发现,Requests 默认发包极易被风控识别拦截,单纯修改请求头完全无法规避。
为搞懂底层识别逻辑,本文从零梳理 TLS 握手、JA3/JA4 指纹核心原理,搭配 Wireshark 抓包对比脚本与浏览器的数据包差异,拆解指纹识别完整链路,最后给出复刻浏览器 TLS 特征、自建请求库的实操方案。
背景科普
在动手抓包之前,科普一些名词。
1. TLS 握手/ClientHello 是什么?
访问
https:// 网站时,在专属数据之前时,客户端会先给服务器发一个 ClientHello的包,里面包含着加密套件,扩展程序这些关键信息,不同版本的客户端携带的内容不一样。2. 为什么能当「指纹」?
关键在于——这个 ClientHello 是明文发的(加密还没建立起来),任何人都能看到里面全部内容。而且不同客户端(Chrome、Firefox、requests)携带的内容都不一致,但每个客户端携带的内容每次都很相似,所以通过这个可以来进行判断客户端。
3. JA3 / JA4 是什么?
客户端发送ClientHello时,将 加密套件、 扩展等相关信息按一定的规则来计算成hash,当作这个客户端的指纹 ID。服务器 / WAF(如 Akamai)手里有一份「已知浏览器指纹库」,来一个请求就算一次指纹去比对:对得上真浏览器就放行,对不上就当成脚本拦掉。
4. 那 requests 为什么老是被认出来?
因为 requests 底层是 OpenSSL,它的 ClientHello 是OpenSSL,对于校验严格会一下识别出来。而且这发生在 TLS 握手阶段,比 HTTP 请求还早—所以你就算把
User-Agent 改成 Chrome 也没用。指纹检测的流程
0. 版本信息
| requests | 2.34.2 |
| :-- | :-- |
| firefox | 152.0.5 |
| chrome | 150.0.7871.115 |
| wireshark | v4.6.7-0-gb439fb7b47a9 |
1. wireshark抓包
一般的抓包软件很难找到相关的请求信息,wireshark作为老牌的网络工具,功能齐全。
打开抓包软件,我在虚拟机进行,捕获的接口为 Ethernet0(以太网)

使用 requests 请求发包
发出请求后,wireshark已经出现
Client Hello相关流量。

抓包的信息相关解释如下表格
| 英文(Wireshark 显示) | 中文名称 |
| :-- | :-- |
| Frame | 帧 / 抓包元信息 |
| Ethernet II | 以太网(链路层帧头) |
| Internet Protocol Version 4 | 网际协议第 4 版(IPv4) |
| Transmission Control Protocol | 传输控制协议(TCP) |
| Transport Layer Security | 传输层安全协议(TLS)重点关注★ |
重点关注
Transport Layer Security 这里。接下来关注firefox和chrome的请求2. Client Hello完整数据包
2.1 Python Requests库
2.2 Firefox
2.3 Chrome
3. 分析 Transport Layer Security
通过第 2 节的
Client Hello 完整数据包一点点来进行分析。3.1 分析Layer以及Handshake
| 字段 | requests | Firefox | Chrome |
| :-- | :-- | :-- | :-- |
| Version | TLS 1.0 (0x0301) | TLS 1.0 (0x0301) | TLS 1.0 (0x0301) |
| Handshake Version | TLS 1.2 (0x0303) | TLS 1.2 (0x0303) | TLS 1.2 (0x0303) |
| Random | 2451d4...(32 字节) | baea10...(32 字节) | a5608c...(32 字节) |
| Session ID | 32 字节 | 32 字节 | 32 字节 |
| Compression | null | null | null |
| Length | 1524 | 1885 | 1750 |
3.2 Cipher Suites(加密套件信息)
| # | requests(17) | Firefox(16) | Chrome(16,含GREASE) |
| :-- | :-- | :-- | :-- |
| 1 | 1302 AES256-GCM | 1301 AES128-GCM | GREASE(0xfafa) |
| 2 | 1303 CHACHA20 | 1303 CHACHA20 | 1301 AES128-GCM |
| 3 | 1301 AES128-GCM | 1302 AES256-GCM | 1302 AES256-GCM |
| 4 | c02c ECDHE-ECDSA-A256 | c02b ECDHE-ECDSA-A128 | 1303 CHACHA20 |
| 5 | c030 ECDHE-RSA-A256 | c02f ECDHE-RSA-A128 | c02b ECDHE-ECDSA-A128 |
| 6 | c02b ECDHE-ECDSA-A128 | cca9 ECDHE-ECDSA-CHACHA | c02f ECDHE-RSA-A128 |
| 7 | c02f ECDHE-RSA-A128 | cca8 ECDHE-RSA-CHACHA | c02c ECDHE-ECDSA-A256 |
| 8 | cca9 ECDHE-ECDSA-CHACHA | c02c ECDHE-ECDSA-A256 | c030 ECDHE-RSA-A256 |
| 9 | cca8 ECDHE-RSA-CHACHA | c030 ECDHE-RSA-A256 | cca9 ECDHE-ECDSA-CHACHA |
| 10 | c024 ECDHE-ECDSA-A256-CBC384 | c00a ECDHE-ECDSA-A256-CBC | cca8 ECDHE-RSA-CHACHA |
| 11 | c028 ECDHE-RSA-A256-CBC384 | c013 ECDHE-RSA-A128-CBC | c013 ECDHE-RSA-A128-CBC |
| 12 | c023 ECDHE-ECDSA-A128-CBC256 | c014 ECDHE-RSA-A256-CBC | c014 ECDHE-RSA-A256-CBC |
| 13 | c027 ECDHE-RSA-A128-CBC256 | 009c RSA-A128-GCM | 009c RSA-A128-GCM |
| 14 | 009f DHE-RSA-A256-GCM | 009d RSA-A256-GCM | 009d RSA-A256-GCM |
| 15 | 009e DHE-RSA-A128-GCM | 002f RSA-A128-CBC | 002f RSA-A128-CBC |
| 16 | 006b DHE-RSA-A256-CBC | 0035 RSA-A256-CBC | 0035 RSA-A256-CBC |
| 17 | 0067 DHE-RSA-A128-CBC | — | — |
3.3 Extensions(扩展插件)
| 顺序 | requests(12,固定序) | Firefox(17,固定序※) | Chrome(18,每次重组) |
| :-- | :-- | :-- | :-- |
| 1 | renegotiation\_info(65281) | server\_name(0) | GREASE(56026) |
| 2 | server\_name(0) | extended\_master\_secret(23) | extended\_master\_secret(23) |
| 3 | ec\_point\_formats(11) | renegotiation\_info(65281) | ALPN(16) |
| 4 | supported\_groups(10) | supported\_groups(10) | renegotiation\_info(65281) |
| 5 | ALPN(16) | ec\_point\_formats(11) | signed\_certificate\_timestamp(18) |
| 6 | encrypt\_then\_mac(22) | session\_ticket(35) | server\_name(0) |
| 7 | extended\_master\_secret(23) | ALPN(16) | psk\_key\_exchange\_modes(45) |
| 8 | post\_handshake\_auth(49) | status\_request(5) | signature\_algorithms(13) |
| 9 | signature\_algorithms(13) | delegated\_credentials(34) | compress\_certificate(27) |
| 10 | supported\_versions(43) | signed\_certificate\_timestamp(18) | ec\_point\_formats(11) |
| 11 | psk\_key\_exchange\_modes(45) | key\_share(51) | supported\_versions(43) |
| 12 | key\_share(51) | supported\_versions(43) | session\_ticket(35) |
| 13 | — | signature\_algorithms(13) | status\_request(5) |
| 14 | — | psk\_key\_exchange\_modes(45) | encrypted\_client\_hello(65037) |
| 15 | — | record\_size\_limit(28) | key\_share(51) |
| 16 | — | compress\_certificate(27) | supported\_groups(10) |
| 17 | — | encrypted\_client\_hello(65037) | application\_settings(17613) |
| 18 | — | — | GREASE(14906) |
※ Firefox 的扩展顺序经 2 次独立抓包验证一致(两次 Random 不同、但 JA3 相同——JA3 不排序扩展,故 JA3 相同即证明顺序未变)。注意 NSS 本身带有扩展乱序机制(见 5.5 节的enableChXtnPermutation),未来版本或开启该 pref 后顺序可能变化,届时以实际抓包为准。Chrome 则是每次请求都重排(所以其 JA3 每次都变,需用会排序的 JA4 才稳定)。
3.4 Supported Groups
| requests(8) | Firefox(7) | Chrome(5) |
| :-- | :-- | :-- |
| X25519MLKEM768 | X25519MLKEM768 | GREASE(0x4a4a) |
| x25519 | x25519 | X25519MLKEM768 |
| secp256r1 | secp256r1 | x25519 |
| x448 | secp384r1 | secp256r1 |
| secp384r1 | secp521r1 | secp384r1 |
| secp521r1 | ffdhe2048 | — |
| ffdhe2048 | ffdhe3072 | — |
| ffdhe3072 | — | — |
3.5 Signature Hash Algorithms
| | requests(26) | Firefox(11) | Chrome(11) |
| :-- | :-- | :-- | :-- |
| \-mldsa | 44/65/87 | — | 含有 44/65/87 |
| ecdsa 256/384/512 | 含有 | 含有 | 256/384(无521) |
| ed25519 / ed448 | 含有 | — | — |
| brainpool tls13 ×3 | 含有 | — | — |
| rsa\_pss\_pss ×3 | 含有 | — | — |
| rsa\_pss\_rsae ×3 | 含有 | 含有 | 含有 |
| rsa\_pkcs1 ×3 | 含有 | 含有 | 含有 |
| sha1 (ecdsa/rsa) | 含有(sha224系列) | 含有 | — |
| DSA sha224/256/384/512 | 含有 | — | — |
3.6 ALPN / 其他关键扩展取值
| 扩展 | requests | Firefox | Chrome |
| :-- | :-- | :-- | :-- |
| ALPN | http/1.1 | h2, http/1.1 | h2, http/1.1 |
| ec\_point\_formats | uncompressed +2 种 | 仅 uncompressed | 仅 uncompressed |
| supported\_versions | 1.3, 1.2 | 1.3, 1.2 | GREASE, 1.3, 1.2 |
| psk\_key\_exchange\_modes | psk\_dhe\_ke | psk\_dhe\_ke | psk\_dhe\_ke |
| encrypt\_then\_mac | 包含 | — | — |
| post\_handshake\_auth | 包含 | — | — |
| session\_ticket | — | 包含 | 包含 |
| status\_request(OCSP) | — | 包含 | 包含 |
| signed\_certificate\_timestamp | — | 包含 | 包含 |
| delegated\_credentials | — | 包含 | — |
| record\_size\_limit | — | 包含(16385) | — |
| compress\_certificate | — | zlib+brotli+zstd | 仅 brotli |
| encrypted\_client\_hello (ECH) | — | 包含 | 包含 |
| application\_settings (ALPS) | — | — | 包含 |
| GREASE | — | — | 包含 |
3.7 key\_share
| requests | Firefox | Chrome |
| :-- | :-- | :-- |
| X25519MLKEM768(1216B) | X25519MLKEM768(1216B) | GREASE(1B) |
| x25519(32B) | x25519(32B) | X25519MLKEM768(1216B) |
| — | secp256r1(65B) | x25519(32B) |
3.8 指纹
| | requests | Firefox | Chrome |
| :-- | :-- | :-- | :-- |
| JA3 | 7291ea5e449f2c7b17582541703e549d | 6447ab086255d194909d4013b1a89e87 | f6c9b33d2cc1d091ec4177e7bd702452 |
| JA4 | t13d1712h1\_ab0a1bf427ad\_882d495ac381 | t13d1617h2\_86a278354501\_3cbfd9057e0d | t13d1516h2\_8daaf6152771\_806a8c22fdea |
JA3 与 JA4 的关键区别:JA3 保留扩展的原始顺序,JA4 会先把扩展排序再计算。 这直接影响到对 Chrome 的识别:Chrome 每次请求都会重排扩展顺序(还带随机 GREASE),所以它的 JA3 每次都变;而 JA4 因为排了序,对 Chrome 依然稳定。反过来,Firefox 扩展顺序固定, JA3 完全相同(见 3.3 节注※)。
| 破绽 | requests | 浏览器(Chrome/Firefox) |
| :-- | :-- | :-- |
| encrypted\_client\_hello | — | 有 |
| ALPN |
http/1.1 | h2, http/1.1 |
| application\_settings | — | Chrome 特有 |
| 签名算法 | 26 个,含 mldsa/ed25519/brainpool/DSA/SHA224 一大堆 | 精简 11 个 |
| x448 群 | 有 | — |
| 独有扩展 | encrypt\_then\_mac、post\_handshake\_auth | — |通过github的源码分析,requests 的请求最终是通过 openssl 来握手、发送的(调用链:requests → urllib3 → Python 标准库 ssl → OpenSSL)。所以上面像 encrypted\_client\_hello、ALPN(h2)、application\_settings 这些,requests(urllib3)默认没有去启用它们;再加上套件/扩展/签名算法用的都是 OpenSSL 的出厂默认排列,所以 ClientHello 数据包一眼就能看出不是浏览器的发包。用
curl_cffi这类开源的tls库,可以避免这些问题,如果开源的被检测了,可以看最后一节,直接动手封装专属自己的库。5. 打造自己的请求库
在前面几个环节分析了requests与浏览器之前的请求差别。而且在第三节分析中,chrome的扩展插件的顺序并不是固定的,所以本次根据firefox来进行封装。
5.1 下载firefox源码
firefox源码地址,我使用的是 firefox151的版本,下载链接:https://ftp.mozilla.org/pub/firefox/releases/151.0.4/source/firefox-151.0.4.source.tar.xz
5.2 定位源码发包位置

netwerk里是firefox的请求地方

security/nss里是握手相关指纹核心。
5.3 指纹
| 指纹要素 | 文件 : 行 | 关键符号 | 说明 |
| :-- | :-- | :-- | :-- |
| ClientHello 主构造 |
security/nss/lib/ssl/ssl3con.c:5505 | ssl3_SendClientHello() | 组装 legacy\_version / random / session\_id / cipher / compression / 扩展 |
| Cipher suites 顺序 (JA3 密码段) | security/nss/lib/ssl/sslenum.c:57 | SSL_ImplementedCiphers[] | NSS 全集,发送序 = 数组序;Firefox 实际发子集(prefs 过滤) |
| ↳ 一致性校验表 | ssl3con.c:213 | cipherSuites[] | 必须与上表同序(源码 assert) |
| 扩展列表 + 顺序 (JA4) | security/nss/lib/ssl/ssl3ext.c:128 | clientHelloSendersTLS[] | ClientHello 扩展发送顺序 |
| ↳ 扩展构造入口 | ssl3ext.c:779 | ssl_ConstructExtensions() | 遍历 senders 表逐个 append |
| ↳ 扩展乱序机制 | ssl3ext.c:793 / :1159 | enableChXtnPermutation / tls_ClientHelloExtensionPermutationSetup() | |
| TLS1.2 扩展编码 | security/nss/lib/ssl/ssl3exthandle.c | ssl3_ClientSend*Xtn | SNI/ALPN/EMS/renego/point\_formats/status\_request… |
| TLS1.3 扩展编码 | security/nss/lib/ssl/tls13exthandle.c | tls13_ClientSend*Xtn | key\_share/supported\_versions/PSK/psk\_modes/cookie/early\_data |
| supported\_groups (曲线) | security/nss/lib/ssl/sslgrp.c | ssl_SendSupportedGroupsXtn | |
| key\_share 曲线 | security/manager/ssl/nsNSSIOLayer.cpp:1600 | | |
| signature\_algorithms | security/nss/lib/ssl/tls13signature.c + ssl3con.c ssl3_SendSigAlgsXtn | | sigalg 列表 |
| ECH (encrypted\_client\_hello) | security/nss/lib/ssl/tls13ech.c / tls13echv.c | tls13_ClientHandleEchXtn | ECH 握手 |
| TLS1.3 握手状态机 | security/nss/lib/ssl/tls13con.c | | ServerHello 后的 1.3 流程 |5.4 Cipher suites
NSS
SSL_ImplementedCiphers[] 全集(发送即此序)。TLS 1.3 三个恒在最前:| 序 | Cipher | Hex |
| :-- | :-- | :-- |
| 1 | TLS\_AES\_128\_GCM\_SHA256 |
0x1301 |
| 2 | TLS\_CHACHA20\_POLY1305\_SHA256 | 0x1303 |
| 3 | TLS\_AES\_256\_GCM\_SHA384 | 0x1302 |
| 4 | TLS\_ECDHE\_ECDSA\_WITH\_AES\_128\_GCM\_SHA256 | 0xc02b |
| 5 | TLS\_ECDHE\_RSA\_WITH\_AES\_128\_GCM\_SHA256 | 0xc02f |
| 6 | TLS\_ECDHE\_ECDSA\_WITH\_CHACHA20\_POLY1305 | 0xcca9 |
| 7 | TLS\_ECDHE\_RSA\_WITH\_CHACHA20\_POLY1305 | 0xcca8 |
| 8 | TLS\_ECDHE\_ECDSA\_WITH\_AES\_256\_GCM\_SHA384 | 0xc02c |
| 9 | TLS\_ECDHE\_RSA\_WITH\_AES\_256\_GCM\_SHA384 | 0xc030 |
| 10 | TLS\_ECDHE\_ECDSA\_WITH\_AES\_256\_CBC\_SHA | 0xc00a |
| 11 | TLS\_ECDHE\_ECDSA\_WITH\_AES\_128\_CBC\_SHA | 0xc009 |
| 12 | TLS\_ECDHE\_RSA\_WITH\_AES\_128\_CBC\_SHA | 0xc013 |
| 13 | TLS\_ECDHE\_RSA\_WITH\_AES\_256\_CBC\_SHA | 0xc014 |
| … | (后续 DHE / ECDH / RSA / 3DES / RC4 / NULL 等大量老套件) | |
| 尾 | TLS\_RSA\_WITH\_AES\_128\_GCM\_SHA256 / …256\_GCM / 128\_CBC\_SHA / 256\_CBC\_SHA … | 0x009c0x009d0x002f0x0035 |5.5 扩展顺序
| 序 | 扩展 | 序 | 扩展 |
| :-- | :-- | :-- | :-- |
| 1 |
tls13_grease (空 GREASE) | 12 | key_share |
| 2 | server_name (SNI) | 13 | early_data |
| 3 | extended_master_secret | 14 | supported_versions |
| 4 | renegotiation_info | 15 | signature_algorithms |
| 5 | supported_groups | 16 | cookie |
| 6 | ec_point_formats | 17 | psk_key_exchange_modes |
| 7 | session_ticket | 18 | post_handshake_auth |
| 8 | application_layer_protocol (ALPN) | 19 | record_size_limit |
| 9 | use_srtp | 20 | certificate_compression |
| 10 | delegated_credentials | 21 | tls13_grease (1 字节 GREASE) |
| 11 | signed_certificate_timestamp (SCT) | 22 | pre_shared_key (必须最后) |5.6 总结
现在有AI的话,直接让AI阅读源码动手封装。我直接使用的是 Rust + PyO3封装好NSS和请求顺序、加密套件等相关指纹信息,然后通过python进行调用即可,目前成功绕过akamai的校验,稳定过。
提示词如下:
上一篇
【看雪转载】基于最新版 Magisk Zygisk,移植实现一个带 WebUI、支持 Android 16 的 Zygisk 等效模块r0zygisk(一)
下一篇
【转载】如何正确看待商业项目:创业者、企业家与投资人的第一套判断框架
- Author:24th
- URL:https://24th.top/article/39ce5b08-46db-80af-8ce4-d07e22931df6
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!






