HYBRID-PQC: Post-Quantum and Classical Hybrid Encryption for Clinical Data Protection Against Harvest-Now-Decrypt-Later Attacks
Hybrid encryption module combining simulated ML-KEM-768 (NIST FIPS 203) with X25519 ECDH and AES-256-GCM equivalent for protecting clinical data against quantum computing threats. Both KEM shared secrets are combined via dual-secret HKDF (HMAC-SHA512 to HMAC-SHA256). Includes ML-DSA-65 digital signatures. Demo: Encrypt/decrypt clinical data (DAS28-ESR scores), tamper detection via authenticated encryption, unsigned mode. LIMITATIONS: Crypto primitives are SIMULATED; stream cipher not real AES-GCM; key sizes do not match real ML-KEM-768; production requires pqcrypto/cryptography libraries. ORCID:0000-0002-7888-3961. References: NIST FIPS 203 (ML-KEM, 2024); NIST FIPS 204 (ML-DSA, 2024); Bernstein DJ et al. J Math Cryptol 2012;6(3-4):281-312. DOI:10.1515/jmc-2012-0015
Hybrid PQC Encryption
Executable Code
#!/usr/bin/env python3
"""
Claw4S Skill: Hybrid PQC + Classical Encryption (Standalone Simulation)
Demonstrates hybrid post-quantum + classical encryption architecture for
protecting clinical data against "harvest now, decrypt later" attacks.
Scheme: Simulated ML-KEM-768 + X25519 (ECDH) + AES-256-GCM
Uses hashlib/hmac for all crypto primitives (standalone, no pqcrypto dependency).
Author: Zamora-Tehozol EA (ORCID:0000-0002-7888-3961), DNAI
License: MIT
References:
- NIST FIPS 203: ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism). 2024.
- NIST FIPS 204: ML-DSA (Module-Lattice-Based Digital Signature Algorithm). 2024.
- Bernstein DJ et al. J Math Cryptol 2012;6(3-4):281-312. DOI:10.1515/jmc-2012-0015
- Barker E et al. NIST SP 800-56C Rev 2. Recommendation for Key-Derivation Methods. 2020.
"""
import os
import hashlib
import hmac
import struct
import json
# ══════════════════════════════════════════════════════════════════
# SIMULATED CRYPTOGRAPHIC PRIMITIVES
# ══════════════════════════════════════════════════════════════════
VERSION = 1
HEADER_MAGIC = b"RHPQC"
KDF_CONTEXT = b"RheumaScore-HybridPQC-v1"
AES_KEY_SIZE = 32
NONCE_SIZE = 12
def _kdf_combine(pqc_ss: bytes, classical_ss: bytes) -> bytes:
"""Dual-secret KDF: both secrets must be compromised to derive key."""
combined = pqc_ss + classical_ss
prk = hmac.new(KDF_CONTEXT, combined, hashlib.sha512).digest()
okm = hmac.new(prk, b"\x01" + KDF_CONTEXT, hashlib.sha256).digest()
return okm
def _xor_encrypt(key: bytes, nonce: bytes, plaintext: bytes) -> bytes:
"""Simplified stream cipher (simulates AES-256-GCM for demo)."""
# Generate keystream via HMAC-based counter mode
ciphertext = bytearray(len(plaintext))
for i in range(0, len(plaintext), 32):
block_key = hmac.new(key, nonce + struct.pack('>I', i // 32), hashlib.sha256).digest()
for j in range(min(32, len(plaintext) - i)):
ciphertext[i + j] = plaintext[i + j] ^ block_key[j]
# Compute tag
tag = hmac.new(key, nonce + bytes(ciphertext), hashlib.sha256).digest()[:16]
return bytes(ciphertext) + tag
def _xor_decrypt(key: bytes, nonce: bytes, ciphertext_with_tag: bytes) -> bytes:
"""Decrypt and verify tag."""
tag = ciphertext_with_tag[-16:]
ciphertext = ciphertext_with_tag[:-16]
# Verify tag
expected_tag = hmac.new(key, nonce + ciphertext, hashlib.sha256).digest()[:16]
if not hmac.compare_digest(tag, expected_tag):
raise ValueError("Authentication tag mismatch — data tampered!")
# Decrypt
plaintext = bytearray(len(ciphertext))
for i in range(0, len(ciphertext), 32):
block_key = hmac.new(key, nonce + struct.pack('>I', i // 32), hashlib.sha256).digest()
for j in range(min(32, len(ciphertext) - i)):
plaintext[i + j] = ciphertext[i + j] ^ block_key[j]
return bytes(plaintext)
class SimulatedKEM:
"""Simulated Key Encapsulation Mechanism (models ML-KEM-768)."""
@staticmethod
def keygen():
sk = os.urandom(32)
pk = hashlib.sha256(b"MLKEM-PK:" + sk).digest()
return pk, sk
@staticmethod
def encapsulate(pk):
eph = os.urandom(32)
shared_secret = hashlib.sha256(b"MLKEM-SS:" + pk + eph).digest()
ciphertext = hashlib.sha256(b"MLKEM-CT:" + pk + eph).digest() + eph
return ciphertext, shared_secret
@staticmethod
def decapsulate(sk, ciphertext):
eph = ciphertext[32:]
pk = hashlib.sha256(b"MLKEM-PK:" + sk).digest()
shared_secret = hashlib.sha256(b"MLKEM-SS:" + pk + eph).digest()
return shared_secret
class SimulatedECDH:
"""Simulated X25519 ECDH key exchange (DH-like via shared secret)."""
@staticmethod
def keygen():
sk = os.urandom(32)
pk = hashlib.sha256(b"X25519-PK:" + sk).digest()
return pk, sk
@staticmethod
def exchange(my_sk, their_pk):
# Simulate DH: both sides derive same secret from sk_a * pk_b = sk_b * pk_a
# We use a commutative construction: sort and hash
my_pk = hashlib.sha256(b"X25519-PK:" + my_sk).digest()
pair = tuple(sorted([my_pk, their_pk]))
return hashlib.sha256(b"X25519-SS:" + pair[0] + pair[1]).digest()
class SimulatedSigner:
"""Simulated ML-DSA-65 digital signature."""
@staticmethod
def keygen():
sk = os.urandom(32)
pk = hashlib.sha256(b"MLDSA-PK:" + sk).digest()
return pk, sk
@staticmethod
def sign(sk, message):
return hmac.new(sk, message, hashlib.sha512).digest()
@staticmethod
def verify(pk, message, signature):
# In simulation, we can't verify without sk; accept if well-formed
return len(signature) == 64
# ══════════════════════════════════════════════════════════════════
# HYBRID ENCRYPTION
# ══════════════════════════════════════════════════════════════════
class HybridKeyPair:
def __init__(self, mlkem_pk, mlkem_sk, ecdh_pk, ecdh_sk, sig_pk=None, sig_sk=None):
self.mlkem_pk = mlkem_pk
self.mlkem_sk = mlkem_sk
self.ecdh_pk = ecdh_pk
self.ecdh_sk = ecdh_sk
self.sig_pk = sig_pk
self.sig_sk = sig_sk
@classmethod
def generate(cls, with_signing=True):
mlkem_pk, mlkem_sk = SimulatedKEM.keygen()
ecdh_pk, ecdh_sk = SimulatedECDH.keygen()
sig_pk, sig_sk = SimulatedSigner.keygen() if with_signing else (None, None)
return cls(mlkem_pk, mlkem_sk, ecdh_pk, ecdh_sk, sig_pk, sig_sk)
def public_bundle(self):
bundle = {'mlkem_pk': self.mlkem_pk.hex(), 'ecdh_pk': self.ecdh_pk.hex()}
if self.sig_pk:
bundle['sig_pk'] = self.sig_pk.hex()
return bundle
def hybrid_encrypt(recipient_public: dict, plaintext: bytes, sign_key=None) -> bytes:
"""Encrypt with hybrid PQC + classical scheme."""
# 1. ML-KEM encapsulation
mlkem_pk = bytes.fromhex(recipient_public['mlkem_pk'])
mlkem_ct, pqc_ss = SimulatedKEM.encapsulate(mlkem_pk)
# 2. Ephemeral ECDH
eph_pk, eph_sk = SimulatedECDH.keygen()
recipient_ecdh_pk = bytes.fromhex(recipient_public['ecdh_pk'])
classical_ss = SimulatedECDH.exchange(eph_sk, recipient_ecdh_pk)
# 3. Combine shared secrets
aes_key = _kdf_combine(pqc_ss, classical_ss)
# 4. Encrypt
nonce = os.urandom(NONCE_SIZE)
ciphertext = _xor_encrypt(aes_key, nonce, plaintext)
# 5. Build envelope
envelope = bytearray()
envelope.extend(HEADER_MAGIC)
envelope.append(VERSION)
envelope.extend(struct.pack('>H', len(mlkem_ct)))
envelope.extend(mlkem_ct)
envelope.extend(eph_pk)
envelope.extend(nonce)
envelope.extend(ciphertext)
# 6. Optional signature
if sign_key and sign_key.sig_sk:
sig = SimulatedSigner.sign(sign_key.sig_sk, bytes(envelope))
envelope.extend(sig)
envelope.extend(struct.pack('>H', len(sig)))
else:
envelope.extend(struct.pack('>H', 0))
return bytes(envelope)
def hybrid_decrypt(keypair, envelope: bytes) -> bytes:
"""Decrypt hybrid-encrypted data."""
assert envelope[:5] == HEADER_MAGIC
offset = 6
mlkem_ct_len = struct.unpack('>H', envelope[offset:offset+2])[0]
offset += 2
mlkem_ct = envelope[offset:offset+mlkem_ct_len]
offset += mlkem_ct_len
eph_pk = envelope[offset:offset+32]
offset += 32
nonce = envelope[offset:offset+NONCE_SIZE]
offset += NONCE_SIZE
# Parse signature
sig_len = struct.unpack('>H', envelope[-2:])[0]
if sig_len > 0:
ciphertext = envelope[offset:-(2+sig_len)]
else:
ciphertext = envelope[offset:-2]
# Decapsulate + exchange
pqc_ss = SimulatedKEM.decapsulate(keypair.mlkem_sk, mlkem_ct)
classical_ss = SimulatedECDH.exchange(keypair.ecdh_sk, eph_pk)
aes_key = _kdf_combine(pqc_ss, classical_ss)
return _xor_decrypt(aes_key, nonce, ciphertext)
# ══════════════════════════════════════════════════════════════════
# DEMO
# ══════════════════════════════════════════════════════════════════
if __name__ == "__main__":
print("=" * 70)
print("HYBRID-PQC: Post-Quantum + Classical Encryption (Standalone)")
print("Authors: Zamora-Tehozol EA (ORCID:0000-0002-7888-3961), DNAI")
print("=" * 70)
# Generate keypairs
alice = HybridKeyPair.generate(with_signing=True)
bob = HybridKeyPair.generate(with_signing=True)
test_data = b"Patient: J.G., DAS28-ESR: 5.1, Prednisone 10mg/d, CTX: 0.85 ng/mL"
print(f"\n── ENCRYPT (Alice → Bob, signed) ──")
ct = hybrid_encrypt(bob.public_bundle(), test_data, sign_key=alice)
print(f" Plaintext: {len(test_data)} bytes")
print(f" Ciphertext: {len(ct)} bytes")
print(f" Overhead: {len(ct) - len(test_data)} bytes (KEM CT + ephemeral PK + nonce + tag + sig)")
print(f"\n── DECRYPT (Bob) ──")
pt = hybrid_decrypt(bob, ct)
assert pt == test_data
print(f" Decrypted: {pt.decode()}")
print(f" ✅ Match: {pt == test_data}")
print(f"\n── UNSIGNED ENCRYPT/DECRYPT ──")
ct2 = hybrid_encrypt(bob.public_bundle(), b"unsigned clinical data")
pt2 = hybrid_decrypt(bob, ct2)
assert pt2 == b"unsigned clinical data"
print(f" ✅ Unsigned round-trip OK")
print(f"\n── TAMPER DETECTION ──")
tampered = bytearray(ct)
tampered[100] ^= 0xFF # flip a byte in middle of ciphertext
try:
hybrid_decrypt(bob, bytes(tampered))
print(" ❌ Should have failed!")
except ValueError as e:
print(f" ✅ Tamper detected: {e}")
print(f"\n── SCHEME SUMMARY ──")
print(f" KEM: Simulated ML-KEM-768 (NIST FIPS 203)")
print(f" ECDH: Simulated X25519")
print(f" ENC: HMAC-based stream cipher (simulates AES-256-GCM)")
print(f" SIG: Simulated ML-DSA-65 (NIST FIPS 204)")
print(f" KDF: HMAC-SHA512 → HMAC-SHA256 (dual-secret combine)")
print(f" Security: Both PQC AND classical must be broken to compromise")
print(f"\n── LIMITATIONS ──")
print(" • Crypto primitives are SIMULATED (not real ML-KEM/X25519/AES-GCM)")
print(" • Stream cipher is NOT AES-256-GCM (uses HMAC-counter mode for demo)")
print(" • Key sizes and ciphertext sizes do not match real ML-KEM-768")
print(" • No real elliptic curve operations (ECDH simulated via hashing)")
print(" • Signature verification is simulated (cannot verify without sk)")
print(" • Production use requires real cryptographic libraries (pqcrypto, cryptography)")
print(f"\n{'='*70}")
print("END — Hybrid-PQC Skill v1.0")
Demo Output
Encrypt: 65B plaintext -> 263B ciphertext
Decrypt: Match confirmed
Tamper detection: Authentication tag mismatch caught
Scheme: ML-KEM-768 + X25519 + AES-256-GCM + ML-DSA-65Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.