Skip to main content

Post-quantum cryptography engine — Hybrid ML-KEM + AES-256-GCM (FIPS 203)

Project description

AegisQ

Post-Quantum Cryptography Engine for Python · v1.2.0

AegisQ is a hybrid cryptographic library that combines ML-KEM (FIPS 203) for quantum-resistant key encapsulation with AES-256-GCM for authenticated symmetric encryption. The cryptographic core is written in Rust for performance and security guarantees, exposed to Python via PyO3.

from aegisq import AegisCipher, SecurityLevel

cipher = AegisCipher(level=SecurityLevel.ML_KEM_768)
keypair = cipher.generate_keypair()

# Encrypt
package = cipher.encrypt(b"Secret data", keypair.public_key)

# Decrypt
plaintext = cipher.decrypt(package, keypair.secret_key)

Features

  • Quantum-safe key exchange — ML-KEM (Module Lattice-based KEM), standardized as NIST FIPS 203
  • Authenticated encryption — AES-256-GCM provides confidentiality and integrity in a single operation
  • Three security levels — ML-KEM-512 (NIST Level 1), ML-KEM-768 (Level 3, default), ML-KEM-1024 (Level 5)
  • Rust core, Python API — Cryptographic operations run in optimized Rust; Python developers get an ergonomic 3-line API
  • Constant-time operations — Timing-attack resistant via subtle::ConstantTimeEq and Barrett reduction
  • Memory zeroization — All secret keys and shared secrets are securely erased after use via zeroize
  • Zero-copy FFI — Data passes between Python and Rust without unnecessary copies
  • GIL release — Rust crypto operations release the Python GIL via py.detach(), enabling true parallelism
  • Type-safe — Full PEP 561 type stubs with IDE autocompletion support
  • Python 3.11+ — Built with PyO3 abi3 stable ABI for broad compatibility (3.11 through 3.13+)
  • Ephemeral sessions with forward secrecyEphemeralSession class auto-generates keypairs and destroys secrets on close
  • Async supportencrypt_async() / decrypt_async() methods for non-blocking cryptographic operations

Installation

From PyPI (recommended)

pip install aegisq-pqc

Python 3.11+ required. The package supports Python 3.11 through 3.13+ via PyO3's stable ABI.

From source (requires Rust toolchain)

# Prerequisites: Rust (via rustup), Python >= 3.11, maturin
pip install maturin

# Clone and build
git clone https://github.com/AC-Santiago/AegisQ.git
cd AegisQ
maturin develop --release

Quick Start

Encrypt and Decrypt (Recommended API)

The AegisCipher class handles the entire hybrid KEM-DEM flow — ML-KEM key encapsulation followed by AES-256-GCM encryption — in a single .encrypt() call.

from aegisq import AegisCipher, SecurityLevel

# 1. Receiver generates a keypair
cipher_bob = AegisCipher(level=SecurityLevel.ML_KEM_768)
keypair = cipher_bob.generate_keypair()
# keypair.public_key  → 1184 bytes (share openly)
# keypair.secret_key  → 2400 bytes (keep private)

# 2. Sender encrypts with the receiver's public key
cipher_alice = AegisCipher(level=SecurityLevel.ML_KEM_768)
encrypted_package = cipher_alice.encrypt(
    plaintext=b"Top secret medical records",
    recipient_public_key=keypair.public_key,
)
# encrypted_package is a single bytes object:
# [ ML-KEM Capsule (1088 B) | Nonce (12 B) | Auth Tag (16 B) | Ciphertext ]

# 3. Receiver decrypts
decrypted = cipher_bob.decrypt(
    encrypted_package=encrypted_package,
    secret_key=keypair.secret_key,
)
assert decrypted == b"Top secret medical records"

Raw KEM Operations (Advanced)

The MlKem class exposes low-level ML-KEM operations for users building custom protocols:

from aegisq import MlKem, SecurityLevel

kem = MlKem(level=SecurityLevel.ML_KEM_768)
keypair = kem.generate_keypair()

# Encapsulate: produces a capsule + 32-byte shared secret
capsule, shared_secret = kem.encapsulate(keypair.public_key)

# Decapsulate: recovers the same 32-byte shared secret
recovered = kem.decapsulate(capsule, keypair.secret_key)
assert shared_secret == recovered

Async Operations

import asyncio
from aegisq import AegisCipher, SecurityLevel

async def main():
    cipher = AegisCipher(level=SecurityLevel.ML_KEM_768)
    keypair = cipher.generate_keypair()

    # Non-blocking encryption
    package = await cipher.encrypt_async(
        b"Secret data",
        keypair.public_key,
    )

    # Non-blocking decryption
    plaintext = await cipher.decrypt_async(
        package,
        keypair.secret_key,
    )
    print(plaintext)  # b'Secret data'

asyncio.run(main())

Ephemeral Sessions (Forward Secrecy)

The EphemeralSession class generates a keypair internally and destroys the secret key when the session closes, providing forward secrecy:

from aegisq import EphemeralSession

# Receiver creates an ephemeral session (secret key never leaves this context)
with EphemeralSession() as receiver:
    public_key = receiver.public_key  # Share this with the sender

    # Sender encrypts using the receiver's public key
    sender_cipher = EphemeralSession()
    package = sender_cipher.encrypt(
        b"Secret data",
        recipient_public_key=public_key,
    )
    sender_cipher.close()

    # Receiver decrypts
    plaintext = receiver.decrypt(package)

# Session closes, secret key is destroyed

Security Levels

Level Enum Value NIST Level Public Key Secret Key Capsule Package Overhead
ML-KEM-512 SecurityLevel.ML_KEM_512 1 800 B 1632 B 768 B 796 B
ML-KEM-768 SecurityLevel.ML_KEM_768 3 (default) 1184 B 2400 B 1088 B 1116 B
ML-KEM-1024 SecurityLevel.ML_KEM_1024 5 1568 B 3168 B 1568 B 1596 B

Package overhead = capsule + AES nonce (12 B) + AES auth tag (16 B). The total encrypted package size is overhead + plaintext length.


API Reference

AegisCipher (recommended for most users)

class AegisCipher:
    def __init__(self, level: SecurityLevel = SecurityLevel.ML_KEM_768) -> None
    def generate_keypair(self) -> KeyPair
    def encrypt(self, plaintext: bytes, recipient_public_key: bytes) -> bytes
    def decrypt(self, encrypted_package: bytes, secret_key: bytes) -> bytes
    async def encrypt_async(self, plaintext: bytes, recipient_public_key: bytes) -> bytes
    async def decrypt_async(self, encrypted_package: bytes, secret_key: bytes) -> bytes

EphemeralSession (forward secrecy)

class EphemeralSession:
    def __init__(self, level: SecurityLevel = SecurityLevel.ML_KEM_768) -> None
    def public_key(self) -> bytes  # Read-only, secret key never exposed
    def encrypt(self, plaintext: bytes, recipient_public_key: bytes) -> bytes
    def decrypt(self, encrypted_package: bytes) -> bytes
    def close(self) -> None
    # Also supports context manager: `with EphemeralSession() as s: ...`

MlKem (advanced, raw KEM operations)

class MlKem:
    def __init__(self, level: SecurityLevel = SecurityLevel.ML_KEM_768) -> None
    def generate_keypair(self) -> KeyPair
    def encapsulate(self, public_key: bytes) -> tuple[bytes, bytes]
    def decapsulate(self, capsule: bytes, secret_key: bytes) -> bytes
    def load_public_key_b64(self, b64: str, level: SecurityLevel = None) -> bytes

Base64 Serialization

# Serialize public key to Base64 URL-safe (no padding)
b64 = keypair.public_key_b64()

# Load public key from Base64 URL-safe string
kem = MlKem(level=SecurityLevel.ML_KEM_768)
public_key_bytes = kem.load_public_key_b64(b64)

KeyPair

class KeyPair:
    public_key: bytes   # Encryption key (share openly)
    secret_key: bytes   # Decapsulation key (keep private)
    level: SecurityLevel

Exceptions

AegisQError(Exception)                          Base exception
├── DecapsulationError(AegisQError)             ML-KEM structural error (wrong buffer size)
├── DecryptionError(AegisQError)               AES-GCM auth tag failed (tampered or wrong key)
├── InvalidParameterError(AegisQError, ValueError)  Incorrect parameter sizes
├── RngError(AegisQError)                      OS CSPRNG unavailable
└── SessionExpiredError(AegisQError)           Attempted operation on closed EphemeralSession

All exceptions can be imported from the top-level package:

from aegisq import AegisQError, DecryptionError

Architecture

AegisQ is structured in three hermetic layers. Each layer only depends on the one below it:

┌─────────────────────────────────────────────────────────────┐
│  Layer 3: Python API  (aegisq/)                             │
│  AegisCipher, MlKem, SecurityLevel, exception hierarchy     │
│  Type hints, docstrings, developer-facing abstractions       │
├─────────────────────────────────────────────────────────────┤
│  Layer 2: FFI Bridge  (crates/aegisq-pyo3/)                 │
│  PyO3 bindings, GIL release, zero-copy data passing          │
│  No cryptographic logic — pure translation layer             │
├─────────────────────────────────────────────────────────────┤
│  Layer 1: Rust Core   (crates/aegisq-core/)                 │
│  ML-KEM (FIPS 203), AES-256-GCM, Transit Package assembly   │
│  #![no_std] compatible, constant-time, zeroize               │
└─────────────────────────────────────────────────────────────┘
  • Layer 1 implements all cryptographic math in pure Rust with no_std compatibility. It has no knowledge of Python.
  • Layer 2 translates Rust types to Python types via PyO3 and releases the GIL during expensive operations.
  • Layer 3 provides the ergonomic Python classes that end users interact with.

Development

Build

maturin develop                    # Debug build (fast compilation)
maturin develop --release          # Release build (optimized)

Test

# Rust tests (unit + integration, all crates)
cargo test --workspace

# Python tests
pytest tests/python/ -v

# Specific test suites
cargo test -p aegisq-core                     # Core crypto only
pytest tests/python/test_cipher_api.py        # AegisCipher end-to-end
pytest tests/python/test_hybrid_bindings.py   # Hybrid bridge
pytest tests/python/test_kem_bindings.py      # KEM bridge
pytest tests/python/test_kem_api.py           # MlKem API

Code Quality

cargo clippy --workspace -- -D warnings   # Rust linter (warnings are errors)
cargo fmt --all                           # Rust formatting
ruff check aegisq/                        # Python type checking

Security

Guarantees

Property Mechanism
IND-CCA2 security Implicit rejection in ML-KEM Decaps (FIPS 203 §7.3)
Quantum resistance M-LWE hardness assumption (ML-KEM)
Data confidentiality + integrity AES-256-GCM authenticated encryption
Timing attack immunity subtle::ConstantTimeEq, Barrett reduction
Memory scrubbing zeroize::Zeroize on all secrets
Nonce uniqueness Fresh 96-bit random nonce via OsRng per encrypt call
Integer overflow protection overflow-checks = true in release profile

Important Notes

  • No forward secrecy by default. If a secret key is compromised, all payloads encrypted to that key are compromised. Mitigation: Use ephemeral keypairs — generate a new keypair per session and discard the secret key after decryption.
  • ML-KEM Decaps never raises an error for invalid capsules (implicit rejection). Instead, it returns a pseudorandom key, which causes AES-GCM to fail with DecryptionError. This prevents chosen-ciphertext oracle attacks.
  • AES-GCM tag failure always raises DecryptionError. Unlike ML-KEM's silent rejection, a failed auth tag means the payload was tampered with or the wrong key was used.

Standards Compliance

Standard Description
FIPS 203 ML-KEM — Module-Lattice-Based Key-Encapsulation Mechanism (NIST, 2024)
NIST SP 800-38D AES-GCM — Galois/Counter Mode specification

Dependencies

Crate Version Purpose
aes-gcm 0.10 AES-256-GCM authenticated encryption (no_std, hardware AES-NI)
sha3 0.11 SHAKE-128/256 and SHA3-256/512 for ML-KEM (no_std)
zeroize 1.8 Secure memory erasure of secrets
subtle 2.6 Constant-time comparisons
getrandom 0.4 Cross-platform CSPRNG (no_std)
pyo3 0.28 Rust-Python FFI bindings (abi3-py311)

For complete technical documentation including the mathematical foundation, algorithm specifications, and security model details, see DOCUMENTATION.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aegisq_pqc-1.2.0.tar.gz (58.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

aegisq_pqc-1.2.0-cp311-abi3-win_amd64.whl (186.2 kB view details)

Uploaded CPython 3.11+Windows x86-64

aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (673.4 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ x86-64

aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (633.2 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

aegisq_pqc-1.2.0-cp311-abi3-macosx_11_0_arm64.whl (297.5 kB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

aegisq_pqc-1.2.0-cp311-abi3-macosx_10_12_x86_64.whl (293.9 kB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

Details for the file aegisq_pqc-1.2.0.tar.gz.

File metadata

  • Download URL: aegisq_pqc-1.2.0.tar.gz
  • Upload date:
  • Size: 58.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aegisq_pqc-1.2.0.tar.gz
Algorithm Hash digest
SHA256 174712ed3b78cbaa7f448c54f9b352ba121a951e3b797b459240fec430f205a3
MD5 74a0193fd92236a1a1209b72ebafdb47
BLAKE2b-256 2698a95ed4844c7bd293516628fc2f5a8ee47541edf7d95cb794140c20c0cf01

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0.tar.gz:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aegisq_pqc-1.2.0-cp311-abi3-win_amd64.whl.

File metadata

  • Download URL: aegisq_pqc-1.2.0-cp311-abi3-win_amd64.whl
  • Upload date:
  • Size: 186.2 kB
  • Tags: CPython 3.11+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aegisq_pqc-1.2.0-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 969e33728d6d7eb286003d1bb92b560aea8a478f0b35e8b6a9a8e6ba276a6bd1
MD5 dab868820ec875c0cad021a841f52775
BLAKE2b-256 927b5dfbe06a04a06a14e2cfdf6d76ca504f7d013fe803a3432e28420f4be46c

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0-cp311-abi3-win_amd64.whl:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9c4acc847c2120b6ac07fa4d3801047db417de124e6df1ade2494a5747d1f044
MD5 fe8b1128aa402f0ae2e4e0c854b729d9
BLAKE2b-256 769897f0a30c24370d2fa8cd38762a2b741dd0855f4cd1b95a63fcab2ea5c055

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 379280237560bd8285a8cde8a0c11bab680546d850707d95f02d95939a1e0d5a
MD5 d5cce27c15e94e199e06dea7ffe538bf
BLAKE2b-256 c37bc4f5478f1f4f9eb65ba33fa5210af161801a2f0e18798bb59d2412853c95

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aegisq_pqc-1.2.0-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.2.0-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 24321299c0eb9eb69083aa401300330479f7816ca4a74993a8a1ba02711d4cde
MD5 152ab12649b1c97a05b2b4c85c3de02f
BLAKE2b-256 f98a84cc98bfdaa9930d06593c6f57220da6432d4307295ab08f3df8b9ec46ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aegisq_pqc-1.2.0-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.2.0-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b1301d58df4c35cf639add684988005e5949fedc085380a709536d80df6d2d94
MD5 ae640bb26965fa176e7f0d67a1b030f1
BLAKE2b-256 cf73981ba035321d8da06a9fe7ff800a212ece0d7e0b18d1cfcd6a00370a897c

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.2.0-cp311-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on AC-Santiago/AegisQ

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page