Skip to main content

hpke-http for Python

hpke_http is the typed Python binding and buffered HTTP integration layer for the sole hpke-http/1 implementation in this repository: the shared Rust engine. The package contains no Python cryptographic implementation and has no fallback when its private extension is absent or mismatched.

Install and runtime support

The package supports CPython 3.10 through 3.14. Release wheels target Linux x86-64 and AArch64 and macOS universal2. Windows is not supported. Other Linux and macOS CPython targets need a Rust source build. PyPy is not supported.

python -m pip install hpke_http
python -m pip install "hpke_http[httpx]"
python -m pip install "hpke_http[aiohttp]"
python -m pip install "hpke_http[fastapi]" fastapi

The fastapi extra installs the middleware's direct Starlette dependency; the example application also declares FastAPI itself. The installed Python package version, Rust engine version, protocol ID, and binding ABI are checked together during import. Any skew is an import error.

Protocol and credentials

The package authenticates complete bounded request and response messages. HTTPS is still required: the protocol does not hide endpoints, recipient-key or PSK identifiers, sizes, or timing.

Recipient keys use X25519 and are 32 bytes. Recipient-key and PSK identifiers are public opaque values from 1 through 255 bytes. A PSK is at least 32 bytes, and its public identifier must not equal the PSK.

Names such as recipient_public_key, request_envelope, resolve_psk, and replay_store in the examples are application-provided key storage, transport, and replay components; the package does not discover them.

from hpke_http import generate_key_pair

key_pair = generate_key_pair()
store_recipient_key(key_pair.private_key, key_pair.public_key)
del key_pair

store_recipient_key is application storage in this example. Python bytes are immutable and cannot be erased in place. Avoid unnecessary secret copies and move generated private keys into protected storage promptly. Closing a client or server releases native copies, not caller-owned byte strings.

Limit Default Hard maximum
Body bytes per message 8 MiB 64 MiB
Combined header-name and value bytes 16 KiB 64 KiB
Header fields per message 64 256
Combined authority and path bytes 8 KiB 8 KiB

Pass Limits to a client, server, or adapter to make a limit stricter. A None field uses the native default.

Protocol body compression is opt-in: set compression="gzip" or "zstd" on Client, HPKEAsyncClient, or HPKEClientSession, and compression=True on Server or HPKEMiddleware. The Rust engine codes only the body and restores it before exposing the logical request or response. It enforces the same body limit before and after decompression. A client that opts in requires an extension-capable server; no silent fallback occurs. Ciphertext length can leak information when attacker input and secrets share a body, so leave compression disabled for those messages.

Low-level client transaction

from hpke_http import Client, Header, Method, Request

with Client(
    recipient_public_key,
    b"primary-2026-09",
    psk,
    b"tenant-42",
) as client:
    transaction = client.protect(
        Request(
            method=Method.POST,
            authority="api.example.test",
            path="/items",
            headers=(Header("content-type", "application/json"),),
            body=b'{"name":"Ada"}',
        )
    )
    try:
        response_envelope = send_envelope(transaction.envelope)
        response = transaction.open_response(response_envelope)
    finally:
        transaction.close()

send_envelope is the application's HTTPS transport in this low-level example. open_response consumes the response continuation. A failed transport attempt must not reuse an earlier envelope: call Client.protect again.

Low-level server and replay admission

Server processing is staged so authenticated plaintext is not released before one atomic replay-store decision:

from hpke_http import Response, Server

with Server(recipient_private_key, b"primary-2026-09") as server:
    preparsed = server.preparse(request_envelope)
    try:
        request_psk = resolve_psk(preparsed.psk_id)
        authenticated = preparsed.authenticate(request_psk)
        try:
            accepted = replay_store.reserve_if_absent(
                authenticated.replay_id,
                authenticated.retain_until_exclusive,
            )
            opened = authenticated.admit(accepted=accepted)
            try:
                logical_response = dispatch(opened.request)
                response_envelope = opened.protect_response(
                    Response(
                        status=logical_response.status,
                        headers=logical_response.headers,
                        body=logical_response.body,
                    )
                )
            finally:
                opened.close()
        finally:
            authenticated.close()
    finally:
        preparsed.close()

The replay operation must atomically reserve an ID if absent across every worker that can receive the same credentials. Keep it through the supplied exclusive Unix deadline. Treat store errors and uncertain outcomes as rejected. The Rust engine checks the trusted clock after the store operation and never releases plaintext at or after the authenticated deadline.

HTTP boundary rules

Authenticated headers are ordered fields with lower-case token names and canonical ASCII values. Repeated names such as set-cookie keep their order.

The core rejects Connection, Expect, Host, Keep-Alive, proxy authentication fields, Proxy-Connection, TE, Trailer, Transfer-Encoding, and Upgrade. High-level adapters also remove each field named by Connection, plus request Accept-Encoding, Content-Length, and Expect, before they construct the logical message.

The adapters accept only absent or identity logical Content-Encoding. They do not decompress authenticated logical bytes outside the body bound. Python client adapters also require identity coding for outer envelopes. The opt-in Rust protocol transform is independent of this HTTP representation field.

A single authenticated Content-Length must equal the body on requests and ordinary responses. It is representation metadata for HEAD and 304, must be zero when present on 205, and is forbidden for 204. HEAD, 204, 205, and 304 responses contain no logical body.

Authenticated Set-Cookie fields remain visible as response fields, but the dedicated outer HTTP clients never store or resend cookies. Logical cookies or authorization are encrypted fields and are never copied to the outer request.

httpx

HPKEAsyncClient returns a fully buffered and authenticated httpx.Response:

from hpke_http.middleware.httpx import HPKEAsyncClient

async with HPKEAsyncClient(
    recipient_public_key,
    b"primary-2026-09",
    psk,
    b"tenant-42",
    base_url="https://api.example.test",
) as client:
    response = await client.post("/items", json={"name": "Ada"})
    response.raise_for_status()

Client default headers and per-request headers belong to the logical request, except for adapter-owned transport fields. Set transport_endpoint to use one fixed HTTPS envelope endpoint. The outer exchange is a separately constructed, non-redirecting POST with message/hpke-http-request; it does not inherit logical headers, cookies, authorization, event hooks, or redirects. Ambient HTTPX proxy and CA settings are disabled; trust_env=True is rejected.

aiohttp

HPKEClientSession is a supported buffered subset, not a drop-in replacement for every aiohttp.ClientSession feature:

from hpke_http.middleware.aiohttp import HPKEClientSession

async with HPKEClientSession(
    recipient_public_key,
    b"primary-2026-09",
    psk,
    b"tenant-42",
    base_url="https://api.example.test/",
) as session:
    async with session.post("/items", json={"name": "Ada"}) as response:
        payload = await response.json()

HPKEResponse exposes authenticated status, headers, URL, method, reason, buffered read, text, and json, status checking, and context-manager compatibility. It has no live socket, streaming body, redirect history, or cookie-jar side effect. Set transport_endpoint to use a fixed envelope endpoint.

FastAPI and Starlette

The ASGI middleware accepts sync or async PSK resolvers and replay admitters. The replay callback receives (replay_id, retain_until_exclusive, scope) and must implement the atomic operation described above. The PSK resolver raises LookupError for an unknown public ID; the middleware returns a generic outer 400. Other resolver failures return outer 503 without exposing callback details.

from fastapi import FastAPI

from hpke_http.middleware.fastapi import HPKEMiddleware

app = FastAPI()
app.add_middleware(
    HPKEMiddleware,
    recipient_private_key=private_key,
    recipient_key_id=b"primary-2026-09",
    psk_resolver=resolve_psk,
    replay_admitter=admit_replay_id,
    expected_authority="api.example.test",
)

The middleware closes its native server when the application's ASGI lifespan ends. With a host that does not send lifespan events, construct HPKEMiddleware(app, ...) directly instead of using add_middleware; retain that wrapper and call close() during host shutdown.

Set transport_path="/_protected" to use one fixed outer endpoint and dispatch the authenticated inner path within the application. Other routes then remain outside this middleware. Without it, every HTTP route is protected and the inner target must match the outer target. In fixed-transport mode, expected_authority is recommended so one endpoint cannot dispatch arbitrary authenticated authorities.

Boundary failures use outer 400 for malformed or unauthenticated input, 405 for a non-POST outer request, 409 for replay or invalid authenticated request time, 415 for invalid envelope media or content coding, 421 for a target mismatch, 500 for an invalid application response, and 503 when credential resolution, replay storage, or the trusted clock is unavailable.

Errors and lifecycle

ProtocolError.code uses stable language-neutral Rust codes:

  • configuration and parsing: invalid_configuration, limit_exceeded, malformed_envelope, unsupported_version, unsupported_suite, and unsupported_method;
  • credentials and authentication: unknown_recipient_key, invalid_credential, and authentication_failed;
  • replay and time: replay_rejected, invalid_request_time, replay_decision_mismatch, and clock_unavailable;
  • platform and local operations: entropy_unavailable, crypto_failure, and compression_failure.

StateError uses state_consumed. TransportError.code distinguishes invalid targets, request and response bounds, network failure, invalid outer status or media type, unsupported outer coding, and unsupported authenticated content coding. TransportError.status_code is present only for an invalid outer HTTP status.

Client and Server support context managers. Every one-shot continuation supports idempotent close(). Close continuations in finally blocks when a host operation can fail before the next stage consumes them.

There are no discovery, SSE, incremental-streaming, Flask, or Django adapters. Whole-message buffering and explicit key configuration are protocol constraints.

Development

From the repository root:

make install-deps-python build-python
make test-python

Release files for hpke-http 2.0.2

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

Source distribution (sdist)

Source distribution for hpke-http 2.0.2
File Size Uploaded
hpke_http-2.0.2.tar.gz 83.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for hpke-http 2.0.2
File Interpreter ABI Platform
hpke_http-2.0.2-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
hpke_http-2.0.2-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
hpke_http-2.0.2-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl CPython 3.10 abi3 macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64, macOS 10.12+ x86-64 Details

Total release size: 2.2 MB

Release files / hpke_http-2.0.2.tar.gz

Download URL hpke_http-2.0.2.tar.gz
Size 83.4 kB
Tags Source
SHA-256 checksum
How to use checksums
8689b688785e44b6fc618c2a4c38d485f0f8642ce68089ed4b162b443a9f42d0
BLAKE2b-256 checksum
How to use checksums
43e30eeddb9e1cfee1995d3043487fda91fc25e2fe687e19370297b2ab314f7a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / hpke_http-2.0.2-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL hpke_http-2.0.2-cp310-abi3-manylinux_2_28_x86_64.whl
Size 582.1 kB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
45bd7a97bb11decfb0d48f32dfb5983385041aa929c4a7d0fa2817d772d73439
BLAKE2b-256 checksum
How to use checksums
8dc29101990ae93d2f3806ab736c4e45e8c05aa420351cdcf6ae0bf475b3dc6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / hpke_http-2.0.2-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL hpke_http-2.0.2-cp310-abi3-manylinux_2_28_aarch64.whl
Size 569.0 kB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
541f4c50a2df79d80ab24af2e2e1abea578eb145e8a09e6b412a0ad4129aca89
BLAKE2b-256 checksum
How to use checksums
3ac183a1756db5785f130e970763e0178e7756b9a4e19bb5bf3eea7ed1861d1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / hpke_http-2.0.2-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL hpke_http-2.0.2-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 985.3 kB
Tags CPython 3.10 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d5a46e78aba2fa127ec0a4b2e4170a593fc719bbb787c9ef08531ec2afe969e9
BLAKE2b-256 checksum
How to use checksums
eb6b0c850bc5ecd8d2daf0fb84c5bdc3d401321e9cf8db7d720f7dac8bbde411
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

4.0.0

4 release files

3.0.0

4 release files

2.1.1

4 release files

2.1.0

4 release files

This release

2.0.2 This release

4 release files

2.0.1

4 release files

2.0.0

4 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.1.2

2 release files

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