Shipping post-quantum cryptography to Python

logickkk11 pts0 comments

Shipping post-quantum cryptography to Python - The Trail of Bits BlogPage content

Post-quantum cryptography is now one pip-install away for the entire Python ecosystem. With funding from the Sovereign Tech Agency, we implemented support for ML-KEM, the NIST-standard key-establishment primitive, and ML-DSA, the NIST-standard digital-signature primitive, in pyca/cryptography.<br>On June 22, 2026, the White House ordered the U.S. government to accelerate its transition to post-quantum cryptography. The order says large-scale quantum computers, especially in adversarial hands, will threaten widely used cryptographic systems, and that attackers may already be collecting encrypted data now so they can decrypt it later. It also sets concrete migration deadlines: high-value and high-impact federal systems must use post-quantum key establishment by December 31, 2030 , and post-quantum digital signatures by December 31, 2031 . And even if you don’t care about quantum resistance, that’s not a problem because quantum resistance isn’t the main benefit of post-quantum crypto.<br>That transition cannot happen only at the policy layer. Every application that signs packages, validates certificates, establishes secure channels, or protects long-lived secrets depends on cryptographic libraries. If those libraries do not expose post-quantum algorithms, the software stack cannot migrate.<br>Almost every Python program that touches cryptography goes through pyca/cryptography. It&rsquo;s currently the eleventh most-downloaded package on PyPI, pulling 1.2 billion downloads in the last month alone. The pyca/cryptography package handles the cryptographic operations of projects like Ansible, Certbot (the Let&rsquo;s Encrypt client), Apache Airflow, paramiko (the Python-only SSH client), and many others. If pyca/cryptography doesn&rsquo;t ship post-quantum primitives, the Python ecosystem can&rsquo;t begin to migrate.<br>Post-quantum support is now one pip install away<br>As of cryptography>=48, support for post quantum algorithms is just a pip install away. The version 48 release includes our Rust bindings for ML-KEM and ML-DSA, the cross binding API and tests, and support for AWS-LC as a cryptographic backend. It also includes work from pyca/cryptography’s maintainers to support the other cryptographic backends. Sadly, this is not enough for a post-quantum migration drop-in swap. These primitives have different size, performance, and integration tradeoffs than the classical algorithms they replace.<br>PQ algorithm tradeoffs<br>Post-quantum primitives keep the same security strength, but they change the size of the data on the wire. Public keys, signatures, and ciphertexts are often 1–2 orders of magnitude larger than the classical values they replace. The operations are also more complex and therefore slower, but on modern hardware they are still imperceptible for regular use, and are likely to get faster with improved hardware and algorithms.<br>For signatures , here&rsquo;s how the classical primitive (Ed25519) compares to its post-quantum equivalent (ML-DSA-65):<br>AlgorithmPublic keyPrivate keyOutputEd2551932 B32 B64 B sigML-DSA-65 1,952 B 32 B 3,309 B sig And for key exchange and encryption , here&rsquo;s how X25519 compares to its post-quantum equivalent (ML-KEM-768):<br>AlgorithmPublic keyPrivate keyOutputX2551932 B32 B32 B sharedML-KEM-768 1,184 B 64 B 1,088 B ciphertext If you maintain a protocol or wire format that hardcodes Ed25519-sized signatures or X25519-sized public keys, the post-quantum migration involves more than a primitive swap. The surrounding fields, length prefixes, and chunking assumptions need to grow with it.<br>Using ML-DSA (FIPS 204): Quantum-resistant signatures<br>ML-DSA is the lattice-based signature scheme that replaces RSA, ECDSA, and Ed25519. The Python API mirrors the existing asymmetric primitives:<br>from cryptography.hazmat.primitives.asymmetric import mldsa

private_key = mldsa.MLDSA65PrivateKey.generate()<br>public_key = private_key.public_key()

signature = private_key.sign(b"message")<br>public_key.verify(signature, b"message") # raises InvalidSignature on failureUsing ML-KEM (FIPS 203): Key encapsulation for the post-quantum era<br>ML-KEM is a key encapsulation mechanism (KEM) for establishing shared secrets. The construction is different, though. ML-KEM is a key encapsulation mechanism, not a Diffie-Hellman exchange. Instead of both parties combining key shares to derive a shared secret, one party encapsulates a fresh shared secret to the receiver’s public key, and the receiver decapsulates it with the matching private key. These operations allow both parties to exchange a secret but in a manner fundamentally different from Diffie-Hellman, and resistant to quantum factoring attacks.<br>from cryptography.hazmat.primitives.asymmetric import mlkem

# Receiver generates a keypair and publishes the public key.<br>private_key = mlkem.MLKEM768PrivateKey.generate()<br>public_key = private_key.public_key()

# Sender encapsulates a fresh shared secret...

quantum post cryptography python rsquo primitives

Related Articles