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.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 hpke-http 2.0.0
File Size Uploaded
hpke_http-2.0.0.tar.gz 83.4 kB Details

Built distributions (wheels)

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

Total release size: 2.2 MB

Release files / hpke_http-2.0.0.tar.gz

Download URL hpke_http-2.0.0.tar.gz
Size 83.4 kB
Tags Source
SHA-256 checksum
How to use checksums
bc19d3e4e867b83e40ca79520dcb4fbadae5e7ccf573db24597f59aa38f6fb1b
BLAKE2b-256 checksum
How to use checksums
14cdbab1d275840307d1bbd659d198fd1336ba98193f09b2479b39fd3d9ec438
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.0-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL hpke_http-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl
Size 581.7 kB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
3abd77cf771dcfff6c08a23993dec112711f1c1c4ae7367743a6afaa8294ec25
BLAKE2b-256 checksum
How to use checksums
98d87f1f72b78447190ca30cc6360b07eab92971fc8cdf6f1722e66559ec8a70
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.0-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL hpke_http-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl
Size 572.0 kB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
0bc04e4fc1de262e74ff916503eed5be8e43af34535deb8f8bddb1f3a42877db
BLAKE2b-256 checksum
How to use checksums
ef864d63658fcfd32fb00cafacda39c28d64f0c1577d8bdd8b5e8d84e288343e
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.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL hpke_http-2.0.0-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
e9de5becb1f6079e525d5111f2e2ebad1203153d12dcc947640decb8a0e37481
BLAKE2b-256 checksum
How to use checksums
363daab8efe21a33edee916ceeb9132fa6f0c0b5f7a2d0ba954fcbc216cff091
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

2.0.2

4 release files

2.0.1

4 release files

This release

2.0.0 This release

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