A crypto-agile secure-transport library with pluggable cipher suites (classical, hybrid X25519+ML-KEM-768, pure-PQC)
Project description
pqchannel
A crypto-agile secure-transport library with pluggable cipher suites.
pqchannel gives an application a secure TCP connection object
(connect() / send() / recv()) where the cryptography is provided by a
pluggable cipher suite — classical, hybrid, or pure post-quantum — swappable
with a single argument.
The one-line swap
from pqchannel import SecureConnection, HybridSuite, ClassicalSuite
# Quantum-safe today...
conn = SecureConnection.connect(("host", 9000), suite=HybridSuite())
# ...swap to classical with ONE line changed, no other app code touched:
conn = SecureConnection.connect(("host", 9000), suite=ClassicalSuite())
That's the whole pitch. The cryptography lives behind an interface — the app never hard-codes an algorithm.
Threat model
Harvest-now-decrypt-later (HNDL): an adversary recording today's classical-only traffic can decrypt it retroactively once a cryptographically-relevant quantum computer exists. The hybrid suite (X25519 + ML-KEM-768) defends against this: even if ML-KEM is broken by a future classical attack, X25519 still holds; even if X25519 is broken by a quantum computer, ML-KEM still holds. Either primitive alone is enough.
Authentication (Phase 4) adds MITM protection: the server signs its handshake material and the client verifies it against a pinned identity key.
Cipher suites
| Suite | Key exchange | Authentication | AEAD | liboqs? |
|---|---|---|---|---|
ClassicalSuite |
X25519 | Ed25519 | AES-256-GCM | no |
HybridSuite |
X25519 + ML-KEM-768 | ML-DSA-65 | AES-256-GCM | yes |
PQOnlySuite |
ML-KEM-768 | ML-DSA-65 | AES-256-GCM | yes |
Install
# From TestPyPI (v1.0.0)
pip install -i https://test.pypi.org/simple/ pqchannel
Post-quantum suites (HybridSuite / PQOnlySuite) require liboqs-python, which
must be compiled against a native liboqs build. The easiest path is Docker —
see SETUP.md for per-OS instructions.
The classical suite (ClassicalSuite) works on any host with only cryptography.
Running it (Docker)
The supported, reproducible environment is Docker — it compiles liboqs
once inside the image so nobody needs a local C toolchain, and it builds
identically on Windows (amd64) and Apple Silicon (arm64). Full setup for both
machines is in SETUP.md.
docker compose build # first time — compiles liboqs (a few minutes)
docker compose run --rm tests # run the full test suite (89 tests)
The classical parts run natively too (they need only cryptography, not
liboqs); the post-quantum tests skip off-Docker. See SETUP.md.
Quick start (library usage)
# server.py
from pqchannel import SecureListener, HybridSuite
with SecureListener(("0.0.0.0", 9000), suite=HybridSuite()) as listener:
conn = listener.accept()
print(conn.recv())
conn.send(b"pong")
# client.py
from pqchannel import SecureConnection, HybridSuite
conn = SecureConnection.connect(("localhost", 9000), suite=HybridSuite())
conn.send(b"ping")
print(conn.recv())
Authenticated handshake
from pqchannel import SecureConnection, HybridSuite, AuthenticationError
# The client pins the server's identity public key (distributed out-of-band).
conn = SecureConnection.connect(
("host", 9000), suite=HybridSuite(), server_pubkey=pinned_pk
)
# AuthenticationError is raised — before any session key is derived — if a
# man-in-the-middle tampered with the server's handshake material.
Examples
Three runnable examples, each using only the public API. Exact two-terminal commands are in SETUP.md → Running the examples.
examples/chat.py— an encrypted chat. Swap the suite live with--suite classical|hybrid|pqonly— this is the crypto-agility story.examples/filetransfer.py— send a file with an end-to-end SHA-256 integrity check.examples/mitm.py— proves the authenticated handshake catches a man-in-the-middle (raisesAuthenticationError).
Benchmarks
benchmarks/bench.py times each suite (keygen / kex / sign / verify / full
authenticated handshake) and measures handshake bytes-on-the-wire:
docker compose run --rm dev python benchmarks/bench.py
| Suite | handshake latency* | handshake bytes on the wire |
|---|---|---|
| Classical | ~0.21 ms | 176 B |
| Hybrid | ~0.22 ms | 7,617 B |
| PQOnly | ~0.14 ms | 7,553 B |
* Crypto only (no TCP), measured in Docker — hardware-specific and noisy, so compare suites relative to each other, not as absolute figures. The wire sizes are exact.
Takeaway: post-quantum security here is fast but large — the PQC
handshakes are ~43× the size of the classical one on the wire, at comparable
CPU cost (the cost of ML-KEM/ML-DSA is bandwidth, not compute). The table, a raw
CSV, and charts are written to benchmarks/results/.
Architecture
Application code
│ suite=HybridSuite()
▼
SecureConnection / SecureListener ← channel.py (public API + AEAD records)
│
▼
Handshake (suite-agnostic) ← handshake.py (KEX + auth, no algo names)
│
├─▶ CipherSuite interface ← suites.py (abstract contract)
│ │
│ ├─ ClassicalSuite (X25519 + Ed25519)
│ ├─ HybridSuite (X25519 + ML-KEM-768 + ML-DSA-65)
│ └─ PQOnlySuite (ML-KEM-768 + ML-DSA-65)
│
└─▶ Framing layer ← framing.py (4-byte length prefix)
Limitations ⚠️
This is a prototyping-grade library for learning and demonstration, not a production security product.
- liboqs is not production-vetted — it is for experimentation.
- Demo-grade authentication. The server's identity key is generated in-process and pinned by the client from that same process — there is no real PKI or out-of-band key distribution. The server signs only its own KEX material (not the full transcript), and there is no mutual (client) auth.
- Counter-based nonces. A direction-partitioned counter drives the AES-GCM nonce — safe for demo scale; a production library would harden this.
- Example-scale. The listener handles one blocking connection at a time (no concurrency model, no data-phase timeouts) — fine for the demos, not a server.
- Benchmark latencies are indicative only (see the note above); the wire sizes are exact.
Building on vetted primitives
This library never implements cryptographic primitives. All crypto comes from:
- liboqs-python — ML-KEM-768, ML-DSA-65
- cryptography — X25519, Ed25519, HKDF-SHA256, AES-256-GCM
Development
docker compose run --rm tests # full test suite (89)
docker compose run --rm dev mypy src/ # type check (strict)
docker compose run --rm dev python benchmarks/bench.py # benchmarks + charts
See SETUP.md for the native (non-Docker) path and per-OS notes.
References
- NIST FIPS 203 (ML-KEM)
- NIST FIPS 204 (ML-DSA)
- NCCoE Migration to Post-Quantum Cryptography
- Open Quantum Safe
Changelog
v1.0.0 (2026-07-04)
- Full crypto-agility story complete:
ClassicalSuite,HybridSuite,PQOnlySuiteall implement the sameCipherSuiteinterface; swap with one argument. - Authenticated handshake with ML-DSA-65 / Ed25519; MITM example (
mitm.py). examples/chat.pyandexamples/filetransfer.pyuse only the public API.benchmarks/bench.pyquantifies handshake size and latency across all three suites.- Published to TestPyPI:
pip install -i https://test.pypi.org/simple/ pqchannel - 91+ pytest tests;
mypy --strict src/clean.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pqchannel-1.0.0.tar.gz.
File metadata
- Download URL: pqchannel-1.0.0.tar.gz
- Upload date:
- Size: 167.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be203b5512e450da6f177cdbe3336c32739656afc202937184262a454269aef4
|
|
| MD5 |
51fc1e95f28f73d808dfedd3c3ef5e9b
|
|
| BLAKE2b-256 |
9dc381173fa7d1e677c07f08ac57d3fa4b2e142c4de7eccd514eaee3502faa3f
|
File details
Details for the file pqchannel-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pqchannel-1.0.0-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
053c82ae1fb8de896e19850bace662bbb1d092c426690cd9ed1ce5f628e34480
|
|
| MD5 |
30bfaf1abaff791d789cfce1df87056d
|
|
| BLAKE2b-256 |
a9440bb331b8276104fb4d45922f81ffa717d2745e1ed23c31cd641430e7dbd2
|