chrome-fp — 与真 Chrome 逐字节同指纹的纯 Python 请求库
用 Python 发 HTTP 请求,但 TLS/HTTP2 指纹跟本机真 Chrome(152.0.7977.64)完全一致: JA4、HTTP/2 Akamai 指纹、请求头顺序、扩展集合、GREASE、ALPS、trust_anchors、PQ 混合密钥共享(X25519MLKEM768)全都一致。
不依赖 curl/Go/浏览器,握手是自己实现的(ssl 模块无法自定义 ClientHello,所以只借用 cryptography 的密码原语)。
实测结果(第三方服务器 tls.peet.ws 判定)
| 项目 | 真 Chrome 152 | 本库 |
|---|---|---|
| JA4 | t13d1517h2_8daaf6152771_cb7bf5808d99 |
完全一致 |
| HTTP/2 Akamai | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
完全一致 |
| 请求头顺序 | :method :authority :scheme :path sec-ch-ua … priority |
完全一致 |
| 扩展数 | 19(17 真实 + 2 GREASE) | 一致 |
离线自检(tools/verify_hello.py)用 26 条真机抓包做基准,43/43 通过:
密码套件列表与顺序、17 个扩展内容逐字节、GREASE 首空尾 1 字节、扩展随机置换、trust_anchors 32 个 ID 集合、
key_share 三项(GREASE 1B / X25519MLKEM768 1216B / x25519 32B)、ECH GREASE 长度集合 {186,218,250,282}、SNI、ALPS、压缩证书、签名算法。
快速开始
cd ~/PycharmProjects/chrome-fp
python3 -m venv .venv && ./.venv/bin/pip install cryptography hpack brotli zstandard certifi
from chrome_fp import Session
s = Session(proxy="http://127.0.0.1:7892") # 可选代理
r = s.get("https://tls.peet.ws/api/all")
d = r.json()
print(d["tls"]["ja4"]) # 与真 Chrome 一致
print(d["http2"]["akamai_fingerprint"])
r = s.post("https://httpbin.org/post", json={"a": 1})
print(r.status_code, r.json())
s.close()
Session 提供 requests 风格 API:get/post/put/delete/head/patch/options、
params/data/json/headers/timeout/allow_redirects、cookie jar、gzip/deflate/br/zstd 自动解压、
HTTP/2 与 HTTP/1.1 自适应(按 ALPN)、代理 CONNECT、证书链校验(浏览器语义:EKU 缺失放行、主机名通配符)。
关键参数
Session(
proxy="http://127.0.0.1:7892", # 代理
verify=True, # 证书链 + 主机名校验
timeout=30, # 秒
mode="navigate", # navigate | cors | no-cors(决定 sec-fetch-*/accept)
headers={"referer": "..."}, # 追加请求头
user_agent=None, # 覆盖 UA
send_priority_tree=True, # 发 Chrome 的 PRIORITY 树(3/5/7/9),Akamai 指纹需要
)
底层构造单条 ClientHello(不含网络):
from chrome_fp import build_client_hello, ja4_from_hello
ch = build_client_hello("example.com")
print(ja4_from_hello(ch.record))
ch.record # 可直接写 socket 的原始字节
include_mlkem=False:不发 X25519MLKEM768(老服务器/只想用 x25519 时)grease_parts=():完全关闭 GREASE;("cipher","ext")之类可细粒度控制permute_extensions=False:关闭扩展随机置换(调试用)
目录结构
chrome_fp/
spec.py Chrome 152 指纹常量(全部标注源码出处)
hello.py 按 BoringSSL ssl_add_clienthello_tlsext 规则拼 ClientHello
fingerprint.py 解析 + JA3/JA4 计算
tls13.py 纯 Python TLS 1.3 客户端(record 层/密钥调度/CV 校验/证书链校验/KeyUpdate)
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,供 X25519MLKEM768 混合密钥共享
http2.py HTTP/2 客户端(Chrome 帧序/SETTINGS/优先级/头顺序)
http1.py HTTP/1.1 客户端(ALPN=http/1.1 时)
session.py requests 风格 API
tools/ 抓包/自检/诊断脚本
tests/ 单元与端到端测试
data/ 真 Chrome 152 的 26 条 ClientHello 抓包 + peet 指纹真值
specs/ 源码提取的规格文档(含出处)
测试
./.venv/bin/python tools/verify_hello.py data/chrome152_clienthello # 43/43
./.venv/bin/python tests/test_mlkem.py # 9/9 (对 pqcrypto/kyber-py 双向互操作)
./.venv/bin/python tools/verify_live.py # 11/11 (实网指纹: JA4/Akamai/头顺序)
./.venv/bin/python tests/test_endtoend.py # 28/28 (本地端到端: TLS1.3 与 TLS1.2 各 14 项)
真实站点实测(20 个):19 个成功(15 个走 TLS 1.3、4 个回落 TLS 1.2),唯一失败是 google.com(见"已知问题")。 证书链校验支持交叉签名根(Google GTS/DigiCert G2 交叉签那类),按 DER 级名称匹配 + 逐级验签 + BFS 建路 + 有效期筛选。
抓真值(需要本地 Chrome):
python3 tools/hello_sniff.py 8899 /tmp/fp_hello # 起 CONNECT 代理并 dump ClientHello
google-chrome --headless=new --proxy-server=http://127.0.0.1:8899 https://example.com/
已知边界与说明
- TLS 1.2 回落已实现(ECDHE + AES-GCM / ChaCha20-Poly1305,含 RSA 密钥交换,支持 EMS/ALPN/证书校验)。 用同一个 ClientHello 回落 —— 真 Chrome 就是这样,所以 JA4 等指纹不变。 实测:nginx.org / bing.com(h2 over TLS1.2)/ sohu.com / qq.com 等只支持 1.2 的站点都能正常请求。 不支持:CBC 类套件(极少见,会明确报错)、会话恢复、0-RTT、客户端证书。
- 已知问题:www.google.com / www.googleapis.com 握手能完成(JA4 一致),但 GFE 随后会用
unexpected_message断链 —— 它在等某个特定形态的客户端消息,本库还没对齐。其余 19/20 个实测站点正常。 (curl / Python ssl 走同一个代理访问 google 正常,所以不是代理问题。) - JA3 哈希每次连接都不同,这是真 Chrome 的行为(扩展顺序随机置换),不是 bug;稳定的标识是 JA4。
- 扩展顺序、GREASE 取值、ECH GREASE 长度、trust_anchors 顺序都是每次连接随机的(与 Chrome 相同分布),所以「逐字节一致」指的是:同一连接内所有非随机字段逐字节相同 + 结构/取值集合与 Chrome 完全一致。
- 会话恢复(PSK / session ticket)、0-RTT、客户端证书、ECH 真加密未实现。
- QUIC/HTTP3 未实现(Chrome 走 TCP 时就是这套指纹)。
真值来源
- 本机真 Chrome 152.0.7977.64 的原始 ClientHello 抓包 26 条(
data/chrome152_clienthello/)+ tls.peet.ws 判定结果(data/chrome152_peet.json) - 本地 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(ML-KEM ss ‖ X25519 ss) - TLS 客户端配置:
/home/wqw/chromium/src/net/socket/ssl_client_socket_impl.cc
- 扩展表顺序与置换:
Metadata
Release files for chrome-fp 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| chrome_fp-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Release files / chrome_fp-0.2.2-py3-none-any.whl
| Download URL | chrome_fp-0.2.2-py3-none-any.whl |
|---|---|
| Size | 48.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
265423b94d2660ea5027ef4f0edfe7b3ff9a93b3fc70655ad54e9ae1772aee46
|
|
BLAKE2b-256 checksum How to use checksums |
5cfbc9c7bc8b73e2bd8c12300b4c2884ed3f4c3aa2f4c2e8a8f3b01482f4f3d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|