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.3.0.tar.gz (70.1 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.3.0-cp311-abi3-win_amd64.whl (206.7 kB view details)

Uploaded CPython 3.11+Windows x86-64

aegisq_pqc-1.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (690.8 kB view details)

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

aegisq_pqc-1.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (654.7 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

aegisq_pqc-1.3.0-cp311-abi3-macosx_11_0_arm64.whl (320.4 kB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

aegisq_pqc-1.3.0-cp311-abi3-macosx_10_12_x86_64.whl (314.6 kB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: aegisq_pqc-1.3.0.tar.gz
  • Upload date:
  • Size: 70.1 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.3.0.tar.gz
Algorithm Hash digest
SHA256 f9b7ad998fd79858708ce12b8226c6314deb2c7260d7cb0becc658bf6a7fcbfc
MD5 18c61c0171740e1fc548bf779d3bdaf4
BLAKE2b-256 5d88a9d55e92174f75b560cacc5993f71255d5f0965e83dfe3e81781fdfb3c7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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.3.0-cp311-abi3-win_amd64.whl.

File metadata

  • Download URL: aegisq_pqc-1.3.0-cp311-abi3-win_amd64.whl
  • Upload date:
  • Size: 206.7 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.3.0-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 d30372db2c98acb4ffc98087a9194276c3767039da22eafe45e5776c3e893684
MD5 408ebe0c15839108c44dfa7945fedd96
BLAKE2b-256 3e9c8574aa3b96f78a288e809c07a50860d20525d0db4b31d426e51cbea2100f

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d8144eb29216a9b846296b89dc725164a9353987ac5105c11bd220a76963b3da
MD5 0b634b6ba6d690965febf68a1c8520dc
BLAKE2b-256 a6d9b9913783dd53590b70c935647b8c694457f1b0f9614d39fe7b5836059629

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 807bf898ac26cb8e60c2e1a4d54082e94e50e2c591091c1027f5a85bea08fb47
MD5 1942c67e87931c0af05cb22ee52dde39
BLAKE2b-256 46b9a38ec847dbb0c007e4a4bbe194ff09224d4d354d5a1e41d24ceac65a9ce2

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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.3.0-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.3.0-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f589bf40e1a8845bac09efdeb6427087a1c4fab2f0f56f16650087c61f23adb0
MD5 a224b32c9839ffcdc48242ba8e072205
BLAKE2b-256 fd0a6409a55e59e86a3e0ff997a565d9d048517be33185318557d3dd015ad197

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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.3.0-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for aegisq_pqc-1.3.0-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 d9523c517e398387bf60cbee1f2f8dad4d2c6f290c0763ba1621c34b4c2660d2
MD5 fc7d8ff2a2005cc5ac2e64d3a810a792
BLAKE2b-256 91357e550c02ebf9c34d829cf1c8cab3d97563c1795812fe9bce3310e2950c3a

See more details on using hashes here.

Provenance

The following attestation bundles were made for aegisq_pqc-1.3.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