Skip to main content

chrome-fp — 与真 Chrome 逐字节同指纹的纯 Python 请求库, 用法和 requests 一样

用 Python 发 HTTP 请求, 但 TLS/HTTP2 指纹跟本机真 Chrome(153.0.8010.48)一致: JA4、HTTP/2 Akamai 指纹、请求头顺序与取值、扩展集合、GREASE、ALPS、trust_anchors、 PQ 混合密钥共享(X25519MLKEM768)全部对齐。

import chrome_fp as requests            # 就当成 requests 用

r = requests.get("https://example.com/", params={"a": 1}, timeout=10)
print(r.status_code, r.headers["Content-Type"], r.elapsed)
print(r.text, r.json() if "json" in r.headers.get("content-type", "") else "")

with requests.Session() as s:
    s.headers.update({"x-token": "abc"})
    r = s.post("https://httpbin.org/post", json={"a": 1})
    r.raise_for_status()

本轮做了什么:拿真机抓包把库对齐

本机是非管理员,Npcap/dumpcap 直接拒绝抓包 (You do not have permission to capture on device)。所以改用应用层旁路抓包:

Chrome ──CONNECT──> tools/tap_proxy.py ──> 真实服务器 / 本地 h2 服务器
                          │
                          ├─ 原样录下双向字节(不改一个字节)
                          ├─ 合成 TCP 头写成 .pcap  ──> tshark / Wireshark 打开
                          └─ Chrome 的 SSLKEYLOGFILE ──> 解密看 HTTP/2 帧

产物在 capture/chrome153/(另有 capture/library/ 是本库走同一流程的抓包,用来对比):

文件 内容
chrome_tap.pcap 合成 TCP 的抓包文件,Wireshark 可直接打开(304 包 / 23 条隧道)
sslkeylog.txt Chrome 自己写的 TLS 会话密钥
hello/*.bin 真 Chrome 发出的 ClientHello 原始字节(17 条样本)
streams/*.bin 每条隧道双向的原始字节流
h2_client.json 解密后解出的请求头(顺序 + 取值)
summary.txt 按资源类型归类的头顺序汇总
tls_diff.txt 与库的逐扩展对比结论(完全一致)
report.json tshark 判定结果(JA4/JA3/扩展/key_share…)

抓包自己也能复现:

python tools/run_capture.py --out capture/chrome153 --sites https://example.com/
python tools/analyze_capture.py --dir capture/chrome153      # tshark 出 JA4 等
python tools/tls13_decrypt.py   capture/chrome153            # 自己解密出 h2 明文
python tools/h2_frames.py       capture/chrome153            # 解 HTTP/2 帧与请求头
python tools/diff_fp.py --dir   capture/chrome153            # 与库逐扩展对比

tshark 4.6 能把服务端方向的 TLS 解出来,但客户端方向一直停在 Encrypted Application Data。抓包本身没问题(record 结构完整、密钥齐全), 所以 tools/tls13_decrypt.py 按 RFC 8446 自己解,并与 tshark 对服务端的判定互相印证。

在 Wireshark / tshark 里直接看

capture/chrome153/chrome_tap.pcap 是标准 pcap(以太网链路层 + 完整 TCP 三次握手, tshark 能正常重组),双击就能用 Wireshark 打开:

# 1) 让 Wireshark 用 Chrome 的 keylog 解密
#    GUI:  编辑 → 首选项 → Protocols → TLS → (Pre)-Master-Secret log filename
#          选 capture/chrome153/sslkeylog.txt
#    tshark 等价写法: -o tls.keylog_file:capture/chrome153/sslkeylog.txt

# 2) 看真 Chrome 的 JA4(tshark 4.6 原生支持)
tshark -r capture/chrome153/chrome_tap.pcap \
       -o tls.keylog_file:capture/chrome153/sslkeylog.txt \
       -Y "tls.handshake.type==1" \
       -T fields -e tls.handshake.extensions_server_name -e tls.handshake.ja4

# 3) 看解密后的 HTTP/2 请求头(顺序就是 Chrome 的真实顺序)
tshark -r capture/chrome153/chrome_tap.pcap \
       -o tls.keylog_file:capture/chrome153/sslkeylog.txt \
       -Y "http2.header.name" -T fields -e http2.streamid -e http2.header.name -e http2.header.value

本机是非管理员,dumpcap.exe -i <任意网卡> 会直接报 You do not have permission to capture on device ... Admin-only Mode, 所以抓包驱动用不了(Npcap 的 NPCAP 组也不在当前令牌里,UAC 又不方便点)。 旁路代理拿到的字节和网卡抓包看到的完全一样,只是没有 TCP 重传之类的噪声; 如果你想要网卡级抓包,用管理员权限跑 dumpcap -i <网卡> -w out.pcapng 即可,分析流程完全一样。

抓出来的差异(已全部修掉)

# 项目 真 Chrome 153 旧库 (0.2.3) 处理
1 trust_anchors(0xca34) ID 数量 28 个 32 个(152 的集合) 去掉 d6790902/03/09/0e 四个
2 HTTP/2 PRIORITY 帧 一个都不发 默认发 3/5/7/9 四帧 send_priority_tree 默认关
3 HEADERS 帧标志 不带 PRIORITY 前缀 带 5 字节 weight 前缀 默认不写
4 Akamai 指纹第 3/4 段 0 / m,a,s,p 会被 #2 改成 00:256,... 修正;m,a,s,p 其实是伪头顺序
5 sec-ch-ua "Google Chrome";v="153", "Not_A Brand";v="8", "Chromium";v="153" 152 的品牌、顺序也不同 更新
6 sec-ch-ua-platform "Windows" "Linux" 更新
7 user-agent Windows NT 10.0; Win64; x64 + Chrome/153 X11; Linux x86_64 + Chrome/152 更新
8 请求头顺序 导航 / 子资源两套不同顺序 只有一套写死的顺序 按 dest/mode 选 profile
9 accept / priority / referer / origin 随资源类型变 全部写死 按 dest 取值
10 sec-fetch-user 只有顶层 document 导航才有 导航一律带 修正

TLS 层其余部分(15 个密码套件、17 个扩展+2 GREASE、11 个签名算法、 4 个 group、ECH GREASE 结构、ALPS、compress_certificate、status_request、 EMS、session_id…)经逐字节比对与 Chrome 153 完全一致。

验证结果

python tools/verify_live.py

让本库走同一个代理访问同一个本地 h2 服务器,再和 Chrome 的抓包对比:

[2/4] TLS ClientHello 对比     密码套件/扩展集合/扩展数量/trust_anchors/groups/sigalgs/versions  全部 OK
[3/4] HTTP/2 指纹对比         Akamai = 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p   一致
                              PRIORITY 帧数量 = 0                                            一致
[4/4] 请求头逐条对比           document / style / font / script / image / iframe / fetch / xhr / POST
                              —— 9 类请求的头顺序与取值全部逐条相同

实测结果

项目 真 Chrome 153 本库
JA4 t13d1517h2_8daaf6152771_<随机> 一致(17/17 条样本)
HTTP/2 Akamai 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p 一致(10/10 条连接)
扩展数 17 真实 + 2 GREASE 一致
trust_anchors 28 个 ID(集合固定、顺序随机) 一致
请求头顺序/取值 导航与子资源各一套 一致(逐条比对)

实网站点:python tools/live_sites.py → 5/5 (example.com / taobao h2+TLS1.3,baidu / qq / sohu TLS1.2 回落 + HTTP/1.1)

requests 兼容的用法

Session / Response 的属性与方法刻意对齐 requests:

import chrome_fp as requests
from chrome_fp.exceptions import HTTPError, TooManyRedirects

s = requests.Session()
s.headers.update({"x-token": "abc"})      # 大小写不敏感, 单次请求头可覆盖
s.params = {"v": "1"}
s.cookies.set("sid", "x", domain="example.com")
s.auth = ("user", "pass")                 # 或 basic_auth/自定义可调用对象
s.proxies = {"https": "http://127.0.0.1:7892"}
s.max_redirects = 10

r = s.request("POST", "https://example.com/api",
              params={"p": 1}, json={"a": 1}, timeout=(5, 20),
              headers={"x-extra": "1"}, cookies={"k": "v"},
              allow_redirects=True, hooks={"response": [lambda r: None]})
r.status_code; r.reason; r.ok; r.headers["CONTENT-TYPE"]; r.text; r.json()
r.content; r.raw; r.url; r.encoding; r.apparent_encoding; r.elapsed; r.history
r.cookies; r.request; r.is_redirect; r.links; r.next
r.raise_for_status(); list(r.iter_content(4096)); list(r.iter_lines())

模块级 chrome_fp.get/post/put/patch/delete/head/options/request 与 requests 同名同签名。 异常层次在 chrome_fp.exceptions(RequestException / HTTPError / Timeout / TooManyRedirects / MissingSchema / InvalidURL / JSONDecodeError …)。

几处刻意的 requests 语义(不是 bug):

  • r.encoding 按 requests 规则来:Content-Type 带 charset 就用它;text/* 没写 charset 时是 ISO-8859-1;application/json 是 utf-8。想要正确的中文文本就 r.encoding = r.apparent_encoding(用 charset_normalizer,和 requests 一样)。
  • r.json() 解析失败抛 chrome_fp.exceptions.JSONDecodeError(同时是 json.JSONDecodeError)。
  • r.iter_lines() 在 decode_unicode=False 时给 bytes。

指纹控制(requests 没有的额外关键字)

Session(
    proxy="http://127.0.0.1:7892",     # 也支持 proxies={...} 和 HTTP(S)_PROXY 环境变量
    mode="cors",                       # navigate | cors | no-cors  —— 决定 sec-fetch-*
    dest="empty",                      # document|iframe|empty|script|style|image|font|preflight
    user_agent=None,                   # 覆盖 UA
    origin=None, referer=None,         # CORS / 子资源请求的上下文
    sec_fetch_site=None,               # none|same-origin|same-site|cross-site
    priority=None,                     # 覆盖 RFC 9218 priority 头
    send_priority_tree=False,          # 只有模拟 Chrome 152 才打开
    verify=True, ca_file=None, allow_tls12=True,
    timeout=30, max_redirects=30, trust_env=True,
    keylog_file=None,                  # 写 NSS key log, Wireshark 可直接解密本库流量
)

单次请求也能覆盖:s.get(url, mode="navigate", dest="document", referer=..., origin=..., priority=...)。

sec-fetch-site 需要调用方给上下文(库不知道"发起方页面"是谁),默认: 顶层 document 导航 = none,其余 = same-origin;跨站时显式传 cross-site。

底层构造单条 ClientHello(不含网络):

from chrome_fp import build_client_hello, ja4_from_hello
ch = build_client_hello("example.com")
print(ja4_from_hello(ch.record), len(ch.record))
ch.record          # 可直接写 socket 的原始字节
  • include_mlkem=False:不发 X25519MLKEM768
  • grease_parts=():完全关闭 GREASE
  • permute_extensions=False:关闭扩展随机置换(调试用)

测试

python -m unittest discover -s tests -v      # 38 个用例
python tools/verify_live.py                  # 端到端: 本库 vs 真 Chrome 抓包
python tools/live_sites.py                   # 实网站点冒烟

tests/test_chrome153_fingerprint.py 直接读 capture/chrome153/ 的真机数据, 把库的 ClientHello 与请求头钉死在真 Chrome 上(没有抓包数据会自动跳过)。

构建

python -m pip install build wheel
python -m build                 # 产出 dist/chrome_fp-0.3.0-py3-none-any.whl 和 .tar.gz
python tools/verify_build.py    # 校验产物(清单/METADATA/隔离安装冒烟/sdist 自举)

verify_build.py 会把 wheel 解到临时目录,用那个副本跑一遍真实功能 (拼 ClientHello、算 JA4、requests 风格 prepare_request),确保校验的不是源码树; 再确认 sdist 解包后能自己重新构建出 wheel。

  • wheel 里只有 chrome_fp 包(14 个模块)+ LICENSE + METADATA
  • sdist 额外带 tests/、tools/,但不含体积大的 capture/ 抓包数据
  • chrome_fp.zip 是同样内容的便携 zip(源码 + README + pyproject + LICENSE)

目录结构

chrome_fp/
  spec.py         Chrome 153 指纹常量 + 每种资源类型的头顺序 profile(全部标注出处)
  hello.py        按 BoringSSL ssl_add_clienthello_tlsext 规则拼 ClientHello
  fingerprint.py  解析 + JA3/JA4 计算
  tls13.py        纯 Python TLS 1.3 客户端(record 层/密钥调度/CV 校验/证书链校验/KeyUpdate/keylog)
  tls12.py        TLS 1.2 回落(ECDHE/RSA + GCM/ChaCha20, EMS, ServerKeyExchange 验签)
  client.py       发同一个 ClientHello, 按 ServerHello 自动分派 1.3 / 1.2
  mlkem.py        ML-KEM-768 (FIPS 203) 纯 Python
  http2.py        HTTP/2 客户端(Chrome 帧序/SETTINGS/头顺序)
  http1.py        HTTP/1.1 客户端(ALPN=http/1.1 时, 头名用 Title-Case)
  session.py      requests 风格的 Session / Response / PreparedRequest
  api.py          模块级 get/post/...(与 requests 同名同签名)
  structures.py   CaseInsensitiveDict / RequestsCookieJar
  exceptions.py   与 requests.exceptions 对应的异常层次
tools/            抓包(代理/本地h2服务器/解密)、分析(tshark/HTTP2)、对比、实网冒烟
tests/            单元 + 与真机抓包的回归测试
capture/          本轮真 Chrome 153 的抓包与判定结果

已知边界

  • TLS 1.2 回落已实现(ECDHE + AES-GCM / ChaCha20-Poly1305,含 RSA 密钥交换、 EMS/ALPN/证书校验)。用同一个 ClientHello 回落,所以 JA4 等指纹不变。 不支持:CBC 类套件(极少见,会明确报错)、会话恢复、0-RTT、客户端证书。
  • 客户端证书未实现:传 cert= 会抛 NotImplementedRequestError(不静默忽略)。
  • files= 的 multipart/form-data 已实现;stream= 只是接收(响应始终缓存, iter_content 仍可用)。
  • 已知问题:www.google.com / www.googleapis.com 握手能完成(JA4 一致), 但 GFE 随后用 unexpected_message 断链 —— 它在等某个特定形态的客户端消息。 其余实测站点正常。
  • JA3 每次连接都不同,这是真 Chrome 的行为(扩展顺序随机置换),不是 bug; 稳定的标识是 JA4。
  • 扩展顺序、GREASE 取值、ECH GREASE 长度、trust_anchors 顺序都是每次连接随机的 (与 Chrome 相同分布)。
  • QUIC/HTTP3 未实现(Chrome 走 TCP 时就是这套指纹)。

真值来源

  1. 本机真 Chrome 153.0.8010.48(Windows x64)的抓包:22 条 ClientHello + 10 条 HTTP/2 连接(capture/chrome153/),JA4 由 tshark 4.6.4 判定
  2. 本地 Chromium/BoringSSL 源码:
    • 扩展表顺序与置换:boringssl/src/ssl/extensions.cc:4067-4295, 4306-4328
    • GREASE 首尾与 padding 规则:extensions.cc:4489-4560
    • ECH GREASE 长度:boringssl/src/ssl/encrypted_client_hello.cc:732-784
    • trust_anchors(0xca34):include/openssl/tls1.h:141 + extensions.cc:2948-2965
    • 混合密钥共享拼接顺序:boringssl/src/ssl/ssl_key_share.cc:308-346
    • TLS 客户端配置:net/socket/ssl_client_socket_impl.cc

Metadata

Release files for chrome-fp 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chrome-fp 0.3.0
File Size Uploaded
chrome_fp-0.3.0.tar.gz 105.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chrome-fp 0.3.0
File Interpreter ABI Platform
chrome_fp-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 172.7 kB

Release files / chrome_fp-0.3.0.tar.gz

Download URL chrome_fp-0.3.0.tar.gz
Size 105.6 kB
Tags Source
SHA-256 checksum
How to use checksums
7eb333f6a6e91a3241ac3c0e7ac56006a9af24fd9fb1c0034e384f9012527d4f
BLAKE2b-256 checksum
How to use checksums
bfc4b6eb971bea764b1ffc52351d8ec92f845936720bf1a66c12796ce2e1cb6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / chrome_fp-0.3.0-py3-none-any.whl

Download URL chrome_fp-0.3.0-py3-none-any.whl
Size 67.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5770be8804305174fb385fde08b4cbb72df076c36bffe7b55658d3b1ecf27def
BLAKE2b-256 checksum
How to use checksums
244b436bd22c11fa5db10901c0610adf0feaba0b0dd8d96a00774d2062efc2ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release history Release notifications | RSS feed

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.3

1 release file

0.2.2

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page