okhttp-tls
English
A Python HTTP client whose TLS ClientHello and HTTP/2 frames match Android OkHttp + Conscrypt — Rust core, synchronous and asyncio interfaces.
On Android, OkHttp's TLS layer is Conscrypt, a Java wrapper around BoringSSL.
This library uses the same TLS library (BoringSSL, via the boring crate)
configured the same way, so matching the configuration means matching the bytes:
same cipher suites and order, same extension order, same signature algorithms
and groups. Profiles built from device samples are checked field by field
against those captures in CI on macOS, Linux x86_64, Linux arm64 and Windows.
Not affiliated with Square, Inc. "OkHttp" is the name of Square's HTTP client; this library reproduces its wire behaviour and ships no OkHttp code.
Features
- OkHttp + Conscrypt TLS shape: 15 cipher suites in BoringSSL's on-device
order, ALPN
h2,http/1.1, OCSPstatus_request, and the post-quantum groupX25519MLKEM768on newer Conscrypt. The cipher order is fixed by the profile, not by the host CPU, so an x86 server emits the device shape exactly - Real OkHttp HTTP/2 fingerprint: SETTINGS carries only
INITIAL_WINDOW_SIZE=16777216, connectionWINDOW_UPDATEof16711681, no PRIORITY frames, pseudo-header order:method :path :authority :scheme, first stream id 3, HPACK indexing and Huffman choices as OkHttp makes them (Akamai h2 fingerprint4:16777216|16711681|0|m,p,a,s) - Protocol chosen by ALPN: h2 when the server selects it, otherwise
HTTP/1.1;
Response.http_versiontells you which - OkHttp header order: your headers go out in the order you give them;
engine headers are appended the way OkHttp's
BridgeInterceptordoes — Content-Type → Content-Length / Transfer-Encoding → Host → Connection → Accept-Encoding → Cookie → User-Agent — and never override ones you set. HTTP/1.1 keeps header-name case; HTTP/2 lowercases names (RFC 9113) - Client behaviour follows OkHttp 4.12 defaults:
- redirects: up to 20; follows 300–303, 307, 308;
Authorizationis dropped on cross-host redirects - connection pool: at most 5 idle connections, kept for 5 minutes
- cookies are sent in insertion order; gzip is decoded transparently; method names are sent verbatim
- timeouts:
connect_timeout/read_timeout/write_timeoutdefault to 10 s each (read/write are the longest idle gap between two I/O operations, not a total);timeoutis the overall cap (default 20 s). A timeout never triggers a resend, so a slow POST is not executed twice
- redirects: up to 20; follows 300–303, 307, 308;
- TLS session resumption on by default, as on a real device. Tickets are
kept per (scheme, host, port, proxy) and never shared across proxies.
Pass
tls_session_reuse=Falseto always send a fresh handshake - Proxy support per client or per request (
http://user:pass@host:port); https targets go through a CONNECT tunnel with preemptive Basic auth - Sync + async: blocking
Clientand asyncio-nativeAsyncClient(Rust tokio runtime underneath) - Embedded CA store (Mozilla bundle), typed (
.pyistub,py.typed), layered exceptions - Prebuilt wheels for macOS (Apple Silicon), Linux (x86_64 and aarch64, manylinux_2_28) and Windows (x86_64); one wheel per platform covers Python ≥ 3.8 (abi3)
Install
pip install okhttp-tls
Usage
Synchronous:
import okhttp_tls
with okhttp_tls.Client(impersonate="okhttp_4_12_0", timeout=20.0) as client:
resp = client.post(
"https://example.com/api",
headers=[("x-api-key", "K"), ("content-type", "application/json")],
content=b'{"q":"x"}',
)
print(resp.status_code, resp.http_version, resp.headers.get("server"), resp.json())
Asynchronous:
import asyncio
import okhttp_tls
async def main():
async with okhttp_tls.AsyncClient() as client:
r1, r2 = await asyncio.gather(
client.get("https://example.com/a"),
client.post("https://example.com/b", json={"k": 1}),
)
asyncio.run(main())
Per-request proxy override:
resp = client.get(url, proxy="http://user:pass@host:port")
Error handling:
try:
resp = client.get(url)
except okhttp_tls.ProxyError:
...
except okhttp_tls.TimeoutException:
...
except okhttp_tls.OkhttpError: # base class of all library errors
...
Profiles
| Short name | Canonical name | Groups | Source |
|---|---|---|---|
okhttp_4_12_0 (default) |
okhttp_4_12_0_conscrypt_pq |
X25519MLKEM768, X25519, P-256, P-384 |
real device |
okhttp_4_11_0 |
okhttp_4_11_0_conscrypt |
X25519, P-256, P-384 |
real device |
okhttp_5_3_2 |
okhttp_5_3_2_conscrypt_pq |
X25519MLKEM768, X25519, P-256, P-384 |
inferred |
impersonate= accepts either name. The default User-Agent is okhttp/<version>.
Why the canonical name carries a Conscrypt part. The fingerprint has two
independent dimensions: the OkHttp version decides the cipher list, ALPN and
TLS version range; the Conscrypt version decides the groups and extensions.
Conscrypt is an Android mainline module updated through Google Play system
updates, independently of the OS version — the same device on the same Android
release went from no post-quantum group to X25519MLKEM768 after such an
update. A version number alone is therefore ambiguous. A short name points to
the profile with real-device evidence for that OkHttp version;
profile_info()["name"] is always the canonical name.
source="inferred": okhttp_5_3_2 has no separate device sample. The
OkHttp 4.12 and 5.3.2 ConnectionSpec cipher lists are identical, so it is
declared an alias of the 4.12 profile (alias_of) instead of inventing a new
fingerprint.
>>> okhttp_tls.profiles()
{'okhttp_4_11_0_conscrypt': 'f3e8b92336e2ffa7b31fcfb9a4bbb617',
'okhttp_4_12_0_conscrypt_pq': 'ce7fd65a5ac2d85d0c89ee0e41a2e8c7',
'okhttp_5_3_2_conscrypt_pq': 'ce7fd65a5ac2d85d0c89ee0e41a2e8c7'}
>>> okhttp_tls.profile_info("okhttp_4_12_0")["ja4"]
't13d1512h2_8daaf6152771_40271e0a5736'
Canonical vs. on-the-wire JA3/JA4. profiles() and profile_info() give
the fingerprint of a fresh connection. A live ClientHello may additionally
carry pre_shared_key (41) when a session is resumed or padding (21) when
the hello length triggers it, and omits key_share / psk_key_exchange_modes
/ supported_versions (51/45/43) if the server only speaks TLS 1.2. These are
added by BoringSSL based on connection state, exactly as on a device; they
change JA3 and JA4, so compare like with like.
API
- Constructors (
Client/AsyncClient):impersonate,proxy,cookie_store(default off),timeout(overall, default 20 s),connect_timeout/read_timeout/write_timeout(default 10 s each;0disables the read/write limit),follow_redirects(default on),max_redirects(default 20),verify(default on),pool(keep-alive, default on),idle_timeout(seconds, default 300),auto_uncompress(gzip, default on),user_agent,tls_session_reuse(default on) - Methods:
get / post / put / delete / patch / head / options / request(method, ...), acceptingheaders(list[tuple]keeps order; adictalso works),content(bytes) orjson(serialized, addscontent-type: application/jsonif absent), andproxy;close()andwith/async with(AsyncClientalso hasaclose()). In asyncio code, useAsyncClientrather thanClient Response:status_code(aliasstatus),http_version("HTTP/1.1"/"HTTP/2"),headers,raw_headers(ordered list of tuples),content(bytes),text,json(),url(final URL after redirects)Headers: case-insensitiveget(name, default),headers[name],name in headers,get_all(name),keys()/values()/items(); iterating and integer indexing yield(name, value)tuplesprofiles()→{canonical name: canonical JA3};profile_info(name)→name,short_name,ja3,ja4,http,alpn,source,evidence,user_agent(plusalias_offor inferred profiles)- Exceptions, all subclasses of
OkhttpError(RuntimeError):BuilderError,ConnectError,ProxyError(proxy unreachable, CONNECT refused, or407),TLSError,TimeoutException,NetworkError,DecodeError,ClientClosedError
The Python API is a superset of the sibling library
dartio-tls (Flutter dart:io
fingerprint): switching between them means changing the module name and the
impersonate profile.
Limitations
- Android Conscrypt only. Desktop/server JVM OkHttp uses SunJSSE, not BoringSSL, and has a completely different ClientHello; it is not covered
- No HTTP/3 / QUIC, no WebSocket, no Cronet/Chromium shape
- HTTP/2 sends one request per connection (no multiplexing). This does not change the TLS or frame fingerprint. Flow control covers what is needed — windowed sending and receive-window refill — with no priority tree and no server push
- TLS 1.3 cipher suites are BoringSSL's fixed three and cannot be changed
- Plain-http targets reached through a proxy do not send
Proxy-Authorization(only CONNECT for https carries credentials) - No wheel for Intel macOS yet
License
MIT
中文
TLS ClientHello 与 HTTP/2 帧形态对齐 Android OkHttp + Conscrypt 的 Python HTTP 客户端——Rust 内核,同步 + asyncio 双面。
Android 上 OkHttp 的 TLS 底座是 Conscrypt,即 BoringSSL 的 Java 封装。本库使用
同一个 TLS 库(BoringSSL,经 boring crate)并按同样方式配置,所以配置对齐即
字节对齐:cipher 套件及顺序、扩展顺序、签名算法、groups 全部一致。来自真机样本的档
都在 CI 里于 macOS、Linux x86_64、Linux arm64、Windows 上与抓包逐字段比对。
与 Square, Inc. 无关联。"OkHttp" 是 Square 的 HTTP 客户端项目名;本库复刻其线上 行为,不包含任何 OkHttp 代码。
特性
- OkHttp + Conscrypt 的 TLS 形态:15 个 cipher 套件按 BoringSSL 真机顺序排列、
ALPN
h2,http/1.1、OCSPstatus_request,新版 Conscrypt 档带后量子组X25519MLKEM768。cipher 顺序由档决定、不看宿主 CPU,x86 服务器上也能精确发出 真机形态 - 真实的 OkHttp HTTP/2 指纹:SETTINGS 只含
INITIAL_WINDOW_SIZE=16777216、 连接级WINDOW_UPDATE为16711681、从不发 PRIORITY、伪头序:method :path :authority :scheme、首流 id 为 3、HPACK 索引与 Huffman 决策与 OkHttp 一致(Akamai h2 指纹4:16777216|16711681|0|m,p,a,s) - 协议由 ALPN 协商决定:服务端选 h2 就走 h2,否则 HTTP/1.1;
Response.http_version可见 - OkHttp 头序:你的请求头按给定顺序原样发出;引擎头按 OkHttp
BridgeInterceptor的方式追加在后——Content-Type → Content-Length / Transfer-Encoding → Host → Connection → Accept-Encoding → Cookie → User-Agent—— 且不覆盖你已给的头。HTTP/1.1 保留头名大小写;HTTP/2 头名全小写(RFC 9113) - 客户端行为对齐 OkHttp 4.12 缺省值:
- 重定向:上限 20 次;跟随 300–303、307、308;跨 host 跳转时剥掉
Authorization - 连接池:最多 5 条空闲连接,保留 5 分钟
- Cookie 按种入顺序发送;gzip 透明解压;方法名原样发送
- 超时:
connect_timeout/read_timeout/write_timeout各缺省 10 秒 (read/write 是两次读写之间的最长空闲,不是总时长);timeout为整体上限 (缺省 20 秒)。超时不会触发重发,慢 POST 不会被执行两次
- 重定向:上限 20 次;跟随 300–303、307、308;跨 host 跳转时剥掉
- TLS 会话复用默认开启,与真机一致。票据按 (scheme, host, port, 代理) 分开保存,
不跨代理共用。传
tls_session_reuse=False则每次都是全新握手 - 代理支持:per-client 或 per-request(
http://user:pass@host:port); https 目标走 CONNECT 隧道,预先携带 Basic 认证 - 同步 + 异步双面:阻塞式
Client与 asyncio 原生AsyncClient(底层为 Rust tokio runtime) - 内嵌 CA store(Mozilla bundle)、带类型(
.pyi存根与py.typed)、 分层异常 - 预编译 wheel:macOS(Apple Silicon)、Linux(x86_64 与 aarch64,manylinux_2_28)、 Windows(x86_64);每平台一个 wheel 通吃 Python ≥ 3.8(abi3)
安装
pip install okhttp-tls
用法
同步:
import okhttp_tls
with okhttp_tls.Client(impersonate="okhttp_4_12_0", timeout=20.0) as client:
resp = client.post(
"https://example.com/api",
headers=[("x-api-key", "K"), ("content-type", "application/json")],
content=b'{"q":"x"}',
)
print(resp.status_code, resp.http_version, resp.headers.get("server"), resp.json())
异步:
import asyncio
import okhttp_tls
async def main():
async with okhttp_tls.AsyncClient() as client:
r1, r2 = await asyncio.gather(
client.get("https://example.com/a"),
client.post("https://example.com/b", json={"k": 1}),
)
asyncio.run(main())
per-request 代理覆盖:
resp = client.get(url, proxy="http://user:pass@host:port")
异常处理:
try:
resp = client.get(url)
except okhttp_tls.ProxyError:
...
except okhttp_tls.TimeoutException:
...
except okhttp_tls.OkhttpError: # 本库所有异常的基类
...
档
| 短名 | 规范名 | Groups | 来源 |
|---|---|---|---|
okhttp_4_12_0 (缺省) |
okhttp_4_12_0_conscrypt_pq |
X25519MLKEM768, X25519, P-256, P-384 |
真机 |
okhttp_4_11_0 |
okhttp_4_11_0_conscrypt |
X25519, P-256, P-384 |
真机 |
okhttp_5_3_2 |
okhttp_5_3_2_conscrypt_pq |
X25519MLKEM768, X25519, P-256, P-384 |
推断 |
impersonate= 两种名字都收。缺省 User-Agent 为 okhttp/<版本>。
为什么规范名要带 Conscrypt 维。 指纹有两个独立维度:OkHttp 版本决定 cipher 列表、
ALPN 与 TLS 版本范围;Conscrypt 版本决定 groups 与扩展集。Conscrypt 是 Android
mainline 模块,随 Google Play 系统更新独立升级,与 OS 版本解耦——同一台设备、同一个
Android 版本,经过一次这样的更新就从没有后量子组变成带 X25519MLKEM768。所以只写版本号
是有歧义的。短名指向该 OkHttp 版本有真机证据的那一档;profile_info()["name"] 恒为规范名。
source="inferred":okhttp_5_3_2 没有独立的真机样本。OkHttp 4.12 与 5.3.2 的
ConnectionSpec cipher 列表逐项相同,因此声明为 4.12 档的别名(alias_of),
而不是凭空造一个新指纹。
>>> okhttp_tls.profiles()
{'okhttp_4_11_0_conscrypt': 'f3e8b92336e2ffa7b31fcfb9a4bbb617',
'okhttp_4_12_0_conscrypt_pq': 'ce7fd65a5ac2d85d0c89ee0e41a2e8c7',
'okhttp_5_3_2_conscrypt_pq': 'ce7fd65a5ac2d85d0c89ee0e41a2e8c7'}
>>> okhttp_tls.profile_info("okhttp_4_12_0")["ja4"]
't13d1512h2_8daaf6152771_40271e0a5736'
规范形与线上 JA3/JA4。 profiles() 与 profile_info() 给的是全新连接的指纹。
线上 ClientHello 可能额外带 pre_shared_key(41)(会话复用时)或 padding(21)
(hello 长度触发时),对端只支持 TLS 1.2 时则没有 key_share /
psk_key_exchange_modes / supported_versions(51/45/43)。这些由 BoringSSL 按连接
状态自动决定,与真机行为一致;它们会改变 JA3 和 JA4,比对时两边要用同一基准。
API
- 构造(
Client/AsyncClient):impersonate、proxy、cookie_store(缺省关)、timeout(整体上限,缺省 20 秒)、connect_timeout/read_timeout/write_timeout(各缺省 10 秒;read/write 设0= 不限)、follow_redirects(缺省开)、max_redirects(缺省 20)、verify(缺省开)、pool(连接复用,缺省开)、idle_timeout(秒,缺省 300)、auto_uncompress(gzip 解压,缺省开)、user_agent、tls_session_reuse(缺省开) - 方法:
get / post / put / delete / patch / head / options / request(任意方法),收headers(list[tuple]保序;dict也收)、content(bytes)或json(自动序列化,未给 content-type 时补content-type: application/json)、proxy;close()与with/async with(AsyncClient另有aclose())。asyncio 代码里请用AsyncClient而不是Client Response:status_code(别名status)、http_version("HTTP/1.1"/"HTTP/2")、headers、raw_headers(保序 tuple 列表)、content(bytes)、text、json()、url(跳转后最终 URL)Headers:大小写不敏感的get(name, default)、headers[name]、name in headers、get_all(name)、keys()/values()/items();迭代与整数下标得到(name, value)tupleprofiles()→{规范名: 规范形 JA3};profile_info(name)→name、short_name、ja3、ja4、http、alpn、source、evidence、user_agent(推断档另有alias_of)- 异常全部继承
OkhttpError(RuntimeError):BuilderError、ConnectError、ProxyError(代理连不上、CONNECT 被拒或407)、TLSError、TimeoutException、NetworkError、DecodeError、ClientClosedError
Python 接口是姊妹库 dartio-tls(Flutter
dart:io 指纹)的超集:两者互换只需改模块名与 impersonate 档。
限制
- 只做 Android Conscrypt 形态。 桌面/服务端 JVM 上的 OkHttp 走 SunJSSE 而非 BoringSSL,ClientHello 完全不同,不在覆盖范围内
- 不做 HTTP/3 / QUIC、WebSocket、Cronet/Chromium 形态
- HTTP/2 每条连接只发一个请求(不做多路复用),不影响 TLS 与帧指纹。流控只做必需部分—— 按窗口分片发送、回补接收窗口——不做优先级树,不做服务端推送
- TLS 1.3 的 cipher 套件是 BoringSSL 固定的三个,不可调
- 明文 http 目标经代理时不发
Proxy-Authorization(只有 https 的 CONNECT 携带凭据) - 暂无 Intel macOS 的 wheel
许可证
MIT
Metadata
Release files for okhttp-tls 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| okhttp_tls-0.1.1-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details for okhttp_tls-0.1.1-cp38-abi3-win_amd64.whl |
| okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.28+ x86-64 | Details for okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_x86_64.whl |
| okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.28+ ARM64 | Details for okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_aarch64.whl |
| okhttp_tls-0.1.1-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details for okhttp_tls-0.1.1-cp38-abi3-macosx_11_0_arm64.whl |
Total release size: 7.3 MB
Release files / okhttp_tls-0.1.1-cp38-abi3-win_amd64.whl
| Download URL | okhttp_tls-0.1.1-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6145a3e783fd3f9227938b91c3455c98ff63bbc017657efef0d7bdd8dae49861
|
|
BLAKE2b-256 checksum How to use checksums |
ae2b59b15569faace8c0d819f4a195120d57a30ce6fc4afb50d5f2c0df3471ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_x86_64.whl
| Download URL | okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | CPython 3.8 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
734097b69a81c1fd0f7a1dacd4208ff0788c009218535ee9396a7c50ad52deb0
|
|
BLAKE2b-256 checksum How to use checksums |
e3ef13b2ca8c24e6d64bb3be8162b67871eed08e781f214d79a43ecdef250034
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_aarch64.whl
| Download URL | okhttp_tls-0.1.1-cp38-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | CPython 3.8 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
e3c46baa7e0efb6002f0168112bf84707ed2aa3ae1ca6adfdc654d526b12e9e4
|
|
BLAKE2b-256 checksum How to use checksums |
351c3149d68d8e42abafc02e7980602f634822a4d65896b40577567b57e08c7f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / okhttp_tls-0.1.1-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | okhttp_tls-0.1.1-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
bd1ad2ce7ae441db9104c3e6520767d9aae0db584052db2f889a27069f5e8a13
|
|
BLAKE2b-256 checksum How to use checksums |
8afac9275c5f0d33b14cd1283aac6311a4597de5aac698876a033123e6df8501
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Security for okhttp-tls 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.