Lazy loaded image
转载
【猿人学转载】Akamai对抗的隐秘战线——TLS指纹
Words 7671Read Time 20 min
2026-7-13
2026-7-13
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(以太网)
notion image
使用 requests 请求发包
发出请求后,wireshark已经出现Client Hello相关流量。
notion image
notion image
抓包的信息相关解释如下表格
| 英文(Wireshark 显示) | 中文名称 | | :-- | :-- | | Frame | 帧 / 抓包元信息 | | Ethernet II | 以太网(链路层帧头) | | Internet Protocol Version 4 | 网际协议第 4 版(IPv4) | | Transmission Control Protocol | 传输控制协议(TCP) | | Transport Layer Security | 传输层安全协议(TLS)重点关注★ |
重点关注 Transport Layer Security 这里。接下来关注firefoxchrome的请求

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 定位源码发包位置

notion image
netwerk里是firefox的请求地方
notion image
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(一)
下一篇
【转载】如何正确看待商业项目:创业者、企业家与投资人的第一套判断框架

Comments
Loading...