Migration Guide — Transition to Post-Quantum Cryptography
This guide provides a practical, step-by-step plan for migrating your applications from traditional cryptography (RSA, ECDSA, AES with classical key exchange) to Qpher post-quantum cryptography (Kyber768 for encryption, Dilithium3 for signatures).
Prerequisites
- A Qpher account (Free tier is sufficient to start)
- An understanding of which cryptographic operations your application currently performs
- Access to modify your application code and data storage schemas
Why Migrate Now?
The "Harvest Now, Decrypt Later" Threat
Nation-state adversaries are actively intercepting and storing encrypted communications today, planning to decrypt them once quantum computers become available. This is known as the "Harvest Now, Decrypt Later" (HNDL) attack. Data encrypted with RSA or ECDH today could be decrypted in 5-10 years. If your data has a sensitivity lifetime longer than that, you need to migrate to PQC now.
Timeline Context
| Year | Milestone |
|---|---|
| 2024 | NIST finalized ML-KEM (Kyber) and ML-DSA (Dilithium) standards |
| 2025 | Early adopters deploying PQC in production |
| 2026 | Government mandates beginning (CNSA 2.0 timeline) |
| 2030 | NIST deprecation deadline for many classical algorithms |
| 2030-2035 | Estimated arrival of cryptographically relevant quantum computers |
Organizations with long-lived data (healthcare, finance, government, legal) should begin migration immediately.
Migration Assessment
Step 1: Inventory Your Cryptographic Usage
Catalog every place your application uses cryptography:
| Category | Traditional | Qpher PQC Replacement |
|---|---|---|
| Data encryption (at rest) | RSA-OAEP, AES with RSA key wrap | Kyber768 KEM (hybrid KEM-DEM) |
| Data encryption (in transit) | TLS with ECDH key exchange | Kyber768 KEM for key agreement |
| Digital signatures | RSA-PSS, ECDSA | Dilithium3 |
| Document signing | RSA, ECDSA | Dilithium3 |
| API authentication | HMAC-SHA256 | HMAC-SHA256 (no change needed) |
| Password hashing | bcrypt, Argon2 | bcrypt, Argon2 (no change needed) |
Symmetric algorithms (AES-256-GCM) and hash-based constructions (HMAC-SHA256, bcrypt) are already quantum-resistant at current key sizes. You only need to replace asymmetric cryptography: RSA, ECDSA, ECDH, and classical key exchange mechanisms.
Step 2: Prioritize by Data Sensitivity
Prioritize migration based on how long your data needs to remain confidential:
| Priority | Data Type | Example | Why |
|---|---|---|---|
| P0 — Immediate | Long-lived secrets | Healthcare records, financial data, legal contracts | Vulnerable to HNDL attack today |
| P1 — High | Business-critical signatures | Signed contracts, audit trails, compliance records | Integrity must hold for decades |
| P2 — Medium | Session data, short-lived tokens | API tokens, session keys | Low risk — short sensitivity window |
| P3 — Low | Public data with integrity | Public announcements, open-source signatures | Can migrate on a relaxed timeline |
Migration Strategies
Strategy A: Direct Replacement (Recommended for New Systems)
Replace traditional cryptography directly with Qpher PQC:
# BEFORE: RSA encryption (vulnerable to quantum)
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
private_key = rsa.generate_private_key(65537, 2048)
public_key = private_key.public_key()
ciphertext = public_key.encrypt(
plaintext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)Strategy B: Hybrid Approach (Recommended for Existing Systems)
During the transition period, encrypt data with both traditional and PQC algorithms. This provides backward compatibility while building quantum resistance:
import base64
import requests
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
def hybrid_encrypt(plaintext: bytes, rsa_public_key, qpher_key_version: int):
"""Encrypt with both RSA and Qpher PQC during transition."""
# Traditional RSA encryption (for backward compatibility)
rsa_ciphertext = rsa_public_key.encrypt(
plaintext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# Qpher PQC encryption (quantum-resistant)
encoded = base64.b64encode(plaintext).decode()
pqc_response = requests.post(
"https://api.qpher.ai/api/v1/kem/encrypt",
headers={
"Content-Type": "application/json",
"x-api-key": "qph_your_key_here",
},
json={
"plaintext": encoded,
"key_version": qpher_key_version,
},
)
pqc_data = pqc_response.json()["data"]
# Store both ciphertexts
return {
"rsa_ciphertext": base64.b64encode(rsa_ciphertext).decode(),
"pqc_ciphertext": pqc_data["ciphertext"],
"pqc_key_version": pqc_data["key_version"],
"encryption_mode": "hybrid",
}Strategy C: Re-Encryption of Historical Data
For existing encrypted data, decrypt with your current system and re-encrypt with Qpher:
import base64
import requests
def migrate_record(record, rsa_private_key, qpher_key_version: int):
"""Migrate a single record from RSA to Qpher PQC."""
# Step 1: Decrypt with existing RSA key
plaintext = rsa_private_key.decrypt(
base64.b64decode(record["rsa_ciphertext"]),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
# Step 2: Re-encrypt with Qpher PQC
encoded = base64.b64encode(plaintext).decode()
response = requests.post(
"https://api.qpher.ai/api/v1/kem/encrypt",
headers={
"Content-Type": "application/json",
"x-api-key": "qph_your_key_here",
},
json={
"plaintext": encoded,
"key_version": qpher_key_version,
},
)
pqc_data = response.json()["data"]
# Step 3: Update the record
record["pqc_ciphertext"] = pqc_data["ciphertext"]
record["pqc_key_version"] = pqc_data["key_version"]
record["migration_status"] = "completed"
return record
# Batch migration
for record in database.query_unmigrated_records(batch_size=100):
migrated = migrate_record(record, rsa_private_key, qpher_key_version=1)
database.update(migrated)
print(f"Migrated record {record['id']}")Migration for Document Signing
Replace RSA/ECDSA Signatures with Dilithium3
# BEFORE: ECDSA signing (vulnerable to quantum)
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
private_key = ec.generate_private_key(ec.SECP256R1())
signature = private_key.sign(document_hash, ec.ECDSA(hashes.SHA256()))Dilithium3 signatures are 3,293 bytes compared to 64 bytes for ECDSA P-256 or 256 bytes for RSA-2048. Plan your storage and bandwidth accordingly. For most applications, this increase is negligible relative to document sizes.
Database Schema Updates
Add columns to store PQC-encrypted data alongside or replacing traditional fields:
-- Add PQC columns to existing tables
ALTER TABLE sensitive_records ADD COLUMN pqc_ciphertext TEXT;
ALTER TABLE sensitive_records ADD COLUMN pqc_key_version INTEGER;
ALTER TABLE sensitive_records ADD COLUMN encryption_type VARCHAR(20)
DEFAULT 'rsa' CHECK (encryption_type IN ('rsa', 'pqc', 'hybrid'));
-- Add PQC signature columns
ALTER TABLE signed_documents ADD COLUMN pqc_signature TEXT;
ALTER TABLE signed_documents ADD COLUMN pqc_sig_key_version INTEGER;
ALTER TABLE signed_documents ADD COLUMN signature_type VARCHAR(20)
DEFAULT 'ecdsa' CHECK (signature_type IN ('ecdsa', 'dilithium3', 'hybrid'));
Recommended Timeline
| Phase | Duration | Activities |
|---|---|---|
| Assessment | 1-2 weeks | Inventory cryptographic usage, prioritize data, create Qpher account |
| Pilot | 2-4 weeks | Migrate one non-critical service, validate performance and correctness |
| Hybrid rollout | 4-8 weeks | Deploy hybrid encryption for P0 data, dual-write both traditional and PQC |
| Full migration | 8-16 weeks | Re-encrypt historical data, migrate remaining services |
| Cleanup | 2-4 weeks | Remove traditional encryption code paths, archive old keys |
Qpher Free tier includes 100 operations per month — enough to prototype and validate your migration plan before committing to a paid plan. Use it for your pilot phase.
Verification Checklist
After migration, verify each of these:
- All new data is encrypted with Qpher Kyber768
- All new signatures use Qpher Dilithium3
- Historical data has been re-encrypted (or hybrid-encrypted)
-
key_versionis stored alongside every ciphertext and signature - Decryption of migrated data returns correct plaintext
- Signature verification succeeds for migrated documents
- Error handling covers Qpher API failures gracefully
- Performance meets your latency requirements (p95: <15ms encrypt, <30ms sign)
- Monitoring and alerting are in place for PQC operations
Error Handling
| HTTP Status | Error Code | Cause | Resolution |
|---|---|---|---|
| 400 | ERR_INVALID_001 | Invalid request format | Check request body matches the API schema |
| 401 | ERR_AUTH_001 | Invalid API key | Verify your API key is active |
| 429 | ERR_RATE_001 | Rate limit exceeded during batch migration | Add delays between batches or upgrade your plan |
| 500 | ERR_INTERNAL_001 | Server error during migration | Retry with exponential backoff |
Related Guides
- Encrypt Data — Encrypt data with Kyber768
- Sign Documents — Sign documents with Dilithium3
- Key Management — Set up your PQC keys
- Key Versioning — Plan key rotation during migration