

#France to Stop Certifying Non‑Quantum‑Safe Encryption
Implementation guide for developers preparing for the 2027 mandate
| Item | Why you need it | Recommended version |
|---|---|---|
| Git | Clone source repos / manage code | ≥ 2.30 |
| Python 3.9+ | Run the Python examples | 3.9‑3.12 |
| Node.js ≥ 18 | Run the JavaScript/TS examples | 18.x LTS |
| CMake ≥ 3.15 (only if you build liboqs from source) | Build the underlying Open Quantum Safe library | 3.15‑3.27 |
| A C/C++ compiler (gcc/clang, MSVC) | Required by liboqs build | gcc ≥ 9 / clang ≥ 10 / MSVC 2019 |
| An OQS‑enabled algorithm (e.g., Kyber768, Dilithium3) | The quantum‑safe primitive you will use | Any algorithm supported by liboqs ≥ 0.10.0 |
| (Optional) Docker | Isolated build environment for CI/CD | 20.10+ |
Note: The Python (
oqs-python) and JavaScript (@open-quantum-safe/nodejs) bindings are thin wrappers around the liboqs C library. Installing the bindings viapip/npmwill automatically pull a pre‑built liboqs binary for most platforms (Linux, macOS, Windows). If you need a custom build (e.g., to enable specific algorithms), follow the liboqs build instructions in the official repo.
# 1️ a virtual environment (optional but recommended)
python -m venv venv
### 2.1 Python
```bash
# Clone the repo (optional – you can work in any folder)
git clone https://github.com/icarax/blog-quantum-safe-demo.git
cd blog-quantum-safe-demo
# Create a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Upgrade pip and install the OQS Python bindings
pip install --upgrade pip
pip install oqs # pulls pre‑built liboqs wheels; falls back to source if needed
# Verify installation
python -c "import oqs; print(oqs.__version__)"
# Initialize a new npm project (if you don’t have one yet)
npm init -y
# Install the OQS Node.js binding (includes a pre‑built liboqs WASM binary)
npm install @open-quantum-safe/nodejs
# Optional: TypeScript support
npm install --save-dev typescript @types/node
npx tsc --init # creates a basic tsconfig.json
Tip: If you see an error like
Cannot find module 'oqs'(Python) orModule not found: Error: Can't resolve '@open-quantum-safe/nodejs'(JS), make sure you are inside the activated virtual environment / have runnpm installin the project root.
Below are complete, copy‑and‑paste ready snippets that demonstrate:
Both languages use the same underlying liboqs algorithms, so the concepts map 1‑to‑1.
#!/usr/bin/env python3
"""
Quantum‑safe demo using liboqs Python bindings.
- Kyber768: Key Encapsulation Mechanism (KEM) for encryption.
- Dilithium3: Digital signature scheme.
"""
import os
import oqs # pip install oqs
# -------------------------------------------------
# Configuration – change these to experiment with other algorithms
# -------------------------------------------------
KEM_ALG = os.getenv("OQS_KEM_ALG", "Kyber768") # NIST‑selected KEM
SIG_ALG = os.getenv("OQS_SIG_ALG", "Dilithium3") # NIST‑selected signature
def demo_kem():
"""Generate a Kyber keypair, encapsulate a secret, then decapsulate."""
print(f"\n=== KEM Demo ({KEM_ALG}) ===")
with oqs.KeyEncapsulation(KEM_ALG) as client:
# Client generates its keypair
public_key = client.generate_keypair()
secret_key = client.export_secret_key()
print(f"Client public key length: {len(public_key)} bytes")
print(f"Client secret key length: {len(secret_key)} bytes")
# Server (or any party) uses the public key to encapsulate a shared secret
with oqs.KeyEncapsulation(KEM_ALG) as server:
ciphertext, shared_secret_server = server.encap_secret(public_key)
print(f"Ciphertext length: {len(ciphertext)} bytes")
print(f"Shared secret (server) length: {len(shared_secret_server)} bytes")
# Client decapsulates to recover the same shared secret
shared_secret_client = client.decap_secret(ciphertext)
print(f"Shared secret (client) length: {len(shared_secret_client)} bytes")
assert shared_secret_client == shared_secret_server, "KEM mismatch!"
print("✅ KEM success: shared secrets match.")
def demo_sig():
"""Generate a Dilithium keypair, sign a message, then verify."""
print(f"\n=== Signature Demo ({SIG_ALG}) ===")
with oqs.Signature(SIG_ALG) as signer:
# Key pair generation
public_key = signer.generate_keypair()
secret_key = signer.export_secret_key()
print(f"Public key length: {len(public_key)} bytes")
print(f"Secret key length: {len(secret_key)} bytes")
# Message to sign
message = b"France mandates quantum‑safe encryption by 2027."
signature = signer.sign(message)
print(f"Signature length: {len(signature)} bytes")
# Verification
verifier = oqs.Signature(SIG_ALG)
verifier.import_public_key(public_key)
assert verifier.verify(message, signature), "Signature verification failed!"
print("✅ Signature success: message verified.")
# Clean up verifier (optional, but good practice)
del verifier
if __name__ == "__main__":
try:
demo_kem()
demo_sig()
except Exception as exc:
print(f"\n❌ Error: {exc}")
raise
What the script does
Both sections include basic error handling (the try/except block in __main__ and assertions that will raise an AssertionError if the cryptographic primitives fail).
// src/demo.ts
import { KeyEncapsulation, Signature } from "@open-quantum-safe/nodejs";
/**
* Environment variables (fallbacks shown)
* OQS_KEM_ALG – e.g., "Kyber768"
* OQS_SIG_ALG – e.g., "Dilithium3"
*/
const KEM_ALG = process.env.OQS_KEM_ALG ?? "Kyber768";
const SIG_ALG = process.env.OQS_SIG_ALG ?? "Dilithium3";
async function runKEMDemo() {
console.log(`\n=== KEM Demo (${KEM_ALG}) ===`);
// ----- Client side -----
const client = new KeyEncapsulation(KEM_ALG);
const clientKeypair = client.generateKeyPair();
const clientPublicKey = clientKeypair.publicKey;
const clientSecretKey = clientKeypair.secretKey;
console.log(`Client public key length: ${clientPublicKey.length} bytes`);
console.log(`Client secret key length: ${clientSecretKey.length} bytes`);
// ----- Server side (encapsulation) -----
const server = new KeyEncapsulation(KEM_ALG);
const encapResult = server.encapSecret(clientPublicKey);
const ciphertext = encapResult.ciphertext;
const sharedSecretServer = encapResult.sharedSecret;
console.log(`Ciphertext length: ${ciphertext.length} bytes`);
console.log(`Shared secret (server) length: ${sharedSecretServer.length} bytes`);
// ----- Client side (decapsulation) -----
const sharedSecretClient = client.decapSecret(ciphertext);
console.log(`Shared secret (client) length: ${sharedSecretClient.length} bytes`);
if (!sharedSecretClient.equals(sharedSecretServer)) {
throw new Error("KEM mismatch: shared secrets differ");
}
console.log("✅ KEM success: shared secrets match.");
// Clean up
client.delete();
server.delete();
}
async function runSignatureDemo() {
console.log(`\n=== Signature Demo (${SIG_ALG}) ===`);
const signer = new Signature(SIG_ALG);
const sigKeypair = signer.generateKeyPair();
const publicKey = sigKeypair.publicKey;
const secretKey = sigKeypair.secretKey;
console.log(`Public key length: ${publicKey.length} bytes`);
console.log(`Secret key length: ${secretKey.length} bytes`);
const message = Buffer.from(
"France mandates quantum‑safe encryption by 2027.",
"utf8"
);
const signature = signer.sign(message);
console.log(`Signature length: ${signature.length} bytes`);
const verifier = new Signature(SIG_ALG);
verifier.importPublicKey(publicKey);
const isValid = verifier.verify(message, signature);
if (!isValid) {
throw new Error("Signature verification failed");
}
console.log("✅ Signature success: message verified.");
// Clean up
signer.delete();
verifier.delete();
}
async function main() {
try {
await runKEMDemo();
await runSignatureDemo();
} catch (err) {
console.error("\n❌ Error:", err);
process.exit(1);
}
}
// Run when executed directly
if (require.main === module) {
main();
}
/* Export for use in other modules */
export { runKEMDemo, runSignatureDemo };
How to run
# Compile TypeScript (if you saved as .ts)
npx tsc demo.ts # produces demo.js
# Or run directly with ts-node (dev only)
npx ts-node demo.ts
# With Node.js (compiled JS)
node demo.js
The JavaScript version mirrors the Python logic:
Both use the buffer‑equal .equals() method for constant‑time comparison (important for side‑channel resistance).
| Variable | Description | Example |
|---|---|---|
OQS_KEM_ALG | KEM algorithm to use (must be supported by liboqs) | Kyber768, Kyber1024, Saber |
OQS_SIG_ALG | Signature algorithm to use | Dilithium2, Dilithium3, Falcon-512 |
OQS_TEMP_DIR (optional) | Directory where the JS/WASM binding extracts temporary files | /tmp/oqs-wasm |
LOG_LEVEL (optional) | Verbosity for debugging (debug, info, warn, error) | info |
Loading the variables
Python – os.getenv("OQS_KEM_ALG", "Kyber768") (see code).
JavaScript – process.env.OQS_KEM_ALG ?? "Kyber768" (see code).
You can store them in a .env file and load with dotenv (Python: python-dotenv; Node: dotenv).
# .env example
OQS_KEM_ALG=Kyber768
OQS_SIG_ALG=Dilithium3
LOG_LEVEL=info
# Python – load .env
from dotenv import load_dotenv
load_dotenv() # reads .env into os.environ
// Node – load .env
require('dotenv').config();
Many standards (e.g., NIST SP 800‑208) recommend hybrid mode: encrypt a symmetric key with both a classical algorithm (RSA/ECDH) and a PQC KEM, then derive the final traffic key via a KDF (HKDF‑SHA256).
# Pseudo‑code (Python)
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
import oqs, os
def hybrid_encap(recipient_pk_classical, recipient_pk_pqc):
# 1. Generate random ephemeral secret
z = os.urandom(32)
# 2. Classical part (e.g., X25519)
# ... use cryptography.hazmat.primitives.asymmetric.x25519 ...
# 3. PQC part
with oqs.KeyEncapsulation("Kyber768") as kem:
ct, ss_pqc = kem.encap_secret(recipient_pk_pqc)
# 4. Combine via HKDF
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"hybrid-encap",
)
key = hkdf.derive(z + ss_classical + ss_pqc) # 32‑byte traffic key
return key, ct # send ct + classical ciphertext
Never use == on secret material. In Python, hmac.compare_digest. In the JS binding, use .equals() on Buffer objects (already constant‑time).
os.urandom, crypto.getRandomValues, secrets.token_bytes).| Symptom | Likely Cause | Fix |
|---|---|---|
ImportError: No module named 'oqs' | Python binding not installed or virtual env not activated | pip install oqs inside the activated venv |
Error: liboqs.so: cannot open shared object file | System missing the shared liboqs library (when building from source) | Install liboqs via package manager (apt-get install liboqs0) or set LD_LIBRARY_PATH to where you built it |
Module not found: Error: Can't resolve '@open-quantum-safe/nodejs' | npm install failed or node_modules missing | Run npm install; ensure you’re in the project root |
AssertionError: shared secrets mismatch | Ciphertext tampered, or using different KEM algorithms on each side | Verify both parties use the same OQS_KEM_ALG; check network transmission integrity |
Signature verification failed | Message altered, or using different signature algorithm | Ensure both sides agree on OQS_SIG_ALG; double‑check that the message bytes are identical (no extra whitespace, encoding differences) |
WASM binding failed to instantiate (JS) | Missing WebAssembly support in old Node.js (< 14) or restrictive CSP | Upgrade Node.js to ≥ 18; if running in browsers, serve the .wasm with correct MIME type (application/wasm) |
Error: unsupported algorithm | Requested algorithm not compiled into liboqs | Re‑install liboqs with the algorithm enabled (./configure --enable-kyber --enable-dilithium) or pick a supported one (oqs.get_enabled_kem_algorithms()) |
Debugging tip:
Enable verbose logging by setting environment variable OQS_LOG_LEVEL=debug (the C library respects this) or wrap calls in try/except and print the exception message.
| ✅ Item | Why it matters | How to verify |
|---|---|---|
| Algorithm selection | Choose NIST‑post‑quantum candidates that have undergone extensive analysis (Kyber, Dilithium, Falcon). | Check OQS_KEM_ALG / OQS_SIG_ALG against NIST PQC standardization status. |
| Side‑channel resistance | liboqs aims for constant‑time implementations, but misuse (e.g., branching on secret data) can re‑introduce leaks. | Run static analysis (e.g., clang-tidy -checks=*security*) and, if possible, side‑channel testing tools (e.g., TestVector from liboqs test suite). |
| Key management | Keys must be stored securely (HSM, KMS, or encrypted at rest). | Verify that private keys never touch logs; use environment variables or secret managers for deployment. |
| Randomness | Poor RNG breaks all security guarantees. | Ensure you use OS CSPRNG (os.urandom, crypto.getRandomValues). |
| Hybrid mode (if required) | Some regulations demand a fallback to classical crypto during transition. | Implement hybrid encapsulation as shown; test that decryption works with either component missing (i.e., treat missing part as all‑zeros). |
| Versioning & agility | Crypto agility lets you swap algorithms without redeploying everything. | Include a key_version field in your message format; test upgrades in a staging environment. |
| Testing vectors | Confirm interoperability with other implementations. | Run liboqs’ built-in test vectors (make test in the liboqs source) and compare outputs. |
| Dependency hygiene | Supply‑chain attacks can compromise the crypto library. | Pin exact versions (pip install oqs==0.10.0, npm install @open-quantum-safe/nodejs@0.10.0), monitor CVE feeds, and consider SBOM generation. |
| Performance benchmark | PQC algorithms are heavier; ensure latency/throughput meets SLA. | Run a realistic load test (e.g., 10k handshakes/sec) and compare against baseline. |
| Documentation & training | Operators need to know how to rotate keys, troubleshoot, and comply with audits. | Produce runbooks, update internal security policies, and conduct tabletop exercises. |
| Legal / compliance | France’s mandate may require specific algorithm profiles or key lengths. | Verify that your chosen algorithms meet the French ANSSI guidelines (e.g., Kyber768 ≥ 128‑bit security). |
Final tip: Treat the post‑quantum crypto layer as a component in a larger security architecture (TLS, SSH, S/MIME, etc.). Whenever possible, rely on well‑defined, prefer using existing PQC‑enabled libraries (e.g., OpenSSL 3.0 with OQS provider, BoringSSL with OQS patch) rather than rolling your own TLS handshake unless you have a dedicated cryptography team.
You now have:
Feel free to copy the code blocks into your own projects, adapt the algorithm names to whatever your organization has standardized on, and start testing your systems against the upcoming 2027 French mandate today.
Stay quantum‑safe! 🚀
Source: Schneier on Security
Follow ICARAX for more AI insights and tutorials.
