quantumshield-proxy 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ """
2
+ QuantumShield Reverse Proxy
3
+ ────────────────────────────
4
+ Post-Quantum Cryptography (ML-KEM-768 / NIST FIPS 203) Reverse Proxy.
5
+ Provides zero-code-change quantum security for web applications.
6
+ """
7
+
8
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,136 @@
1
+ """
2
+ classical_encryptor.py
3
+ ──────────────────────────────────────────────────────────────────────────────
4
+ RSA-2048 key generation, encryption, and decryption with timing.
5
+
6
+ This is the QUANTUM-VULNERABLE baseline for comparison with CRYSTALS-Kyber-768.
7
+ RSA security relies on the integer factorization problem — solvable in
8
+ polynomial time by Shor's Algorithm on a sufficiently powerful quantum computer.
9
+
10
+ Important: RSA-OAEP can only encrypt data smaller than the key size minus
11
+ padding overhead (~190 bytes max for RSA-2048 with OAEP-SHA256).
12
+ In a real hybrid system you would encrypt a symmetric key with RSA, then
13
+ encrypt the actual data with AES. Here we truncate to 190 bytes to keep
14
+ the benchmark focused on RSA timing characteristics.
15
+ ──────────────────────────────────────────────────────────────────────────────
16
+ """
17
+
18
+ from cryptography.hazmat.primitives.asymmetric import rsa, padding
19
+ from cryptography.hazmat.primitives import hashes
20
+ import time
21
+
22
+
23
+ class ClassicalEncryptor:
24
+ """RSA-2048 encryptor using OAEP-SHA256 padding."""
25
+
26
+ def __init__(self):
27
+ self.private_key = None
28
+ self.public_key = None
29
+ self.key_size = 2048
30
+
31
+ def generate_keypair(self) -> dict:
32
+ """
33
+ Generate a fresh RSA-2048 key pair.
34
+ Returns key gen time in milliseconds and key size in bits.
35
+ """
36
+ start = time.perf_counter()
37
+ self.private_key = rsa.generate_private_key(
38
+ public_exponent=65537,
39
+ key_size=self.key_size,
40
+ )
41
+ self.public_key = self.private_key.public_key()
42
+ elapsed = (time.perf_counter() - start) * 1000
43
+
44
+ return {
45
+ "key_gen_time_ms": round(elapsed, 3),
46
+ "key_size_bits": self.key_size,
47
+ "quantum_resistant": False,
48
+ "quantum_security_bits": 0,
49
+ "algorithm": "RSA-2048",
50
+ "padding": "OAEP-SHA256",
51
+ }
52
+
53
+ def encrypt(self, data: bytes) -> dict:
54
+ """
55
+ Encrypt data with RSA-OAEP.
56
+ Truncates input to 190 bytes — the max payload for RSA-2048 + OAEP-SHA256.
57
+ Returns ciphertext hex, encryption time, and ciphertext size.
58
+ """
59
+ if self.public_key is None:
60
+ raise RuntimeError("Call generate_keypair() before encrypt()")
61
+
62
+ # RSA-2048 with OAEP-SHA256: max plaintext = (2048/8) - 2*32 - 2 = 190 bytes
63
+ data_chunk = data[:190]
64
+
65
+ start = time.perf_counter()
66
+ ciphertext = self.public_key.encrypt(
67
+ data_chunk,
68
+ padding.OAEP(
69
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
70
+ algorithm=hashes.SHA256(),
71
+ label=None,
72
+ ),
73
+ )
74
+ elapsed = (time.perf_counter() - start) * 1000
75
+
76
+ return {
77
+ "ciphertext": ciphertext.hex(),
78
+ "encryption_time_ms": round(elapsed, 3),
79
+ "ciphertext_size_bytes": len(ciphertext),
80
+ "plaintext_size_bytes": len(data_chunk),
81
+ }
82
+
83
+ def decrypt(self, ciphertext_hex: str) -> dict:
84
+ """
85
+ Decrypt RSA-OAEP ciphertext.
86
+ Returns recovered plaintext and decryption time.
87
+ """
88
+ if self.private_key is None:
89
+ raise RuntimeError("Call generate_keypair() before decrypt()")
90
+
91
+ start = time.perf_counter()
92
+ plaintext = self.private_key.decrypt(
93
+ bytes.fromhex(ciphertext_hex),
94
+ padding.OAEP(
95
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
96
+ algorithm=hashes.SHA256(),
97
+ label=None,
98
+ ),
99
+ )
100
+ elapsed = (time.perf_counter() - start) * 1000
101
+
102
+ return {
103
+ "plaintext": plaintext.decode("utf-8", errors="replace"),
104
+ "decryption_time_ms": round(elapsed, 3),
105
+ }
106
+
107
+
108
+ # ─── Self-test when run directly ──────────────────────────────────────────────
109
+
110
+ if __name__ == "__main__":
111
+ enc = ClassicalEncryptor()
112
+
113
+ print("🔑 Generating RSA-2048 key pair...")
114
+ keygen = enc.generate_keypair()
115
+ print(f" Key gen time : {keygen['key_gen_time_ms']} ms")
116
+ print(f" Key size : {keygen['key_size_bits']} bits")
117
+ print(f" Quantum safe : {keygen['quantum_resistant']}")
118
+ assert keygen["key_gen_time_ms"] > 0
119
+ assert keygen["key_size_bits"] == 2048
120
+
121
+ test_data = b"UPI|Aarav Sharma->Priya Patel|4500|SBI|2026-01-03T10:15:30Z"
122
+ print(f"\n🔒 Encrypting: {test_data[:60]}")
123
+ enc_result = enc.encrypt(test_data)
124
+ print(f" Encrypt time : {enc_result['encryption_time_ms']} ms")
125
+ print(f" Ciphertext size : {enc_result['ciphertext_size_bytes']} bytes")
126
+ print(f" Ciphertext (hex) : {enc_result['ciphertext'][:40]}...")
127
+ assert enc_result["ciphertext_size_bytes"] == 256 # RSA-2048 always 256 bytes
128
+ assert len(enc_result["ciphertext"]) == 512 # hex = 2 chars per byte
129
+
130
+ print(f"\n🔓 Decrypting ciphertext...")
131
+ dec_result = enc.decrypt(enc_result["ciphertext"])
132
+ print(f" Decrypt time : {dec_result['decryption_time_ms']} ms")
133
+ print(f" Recovered : {dec_result['plaintext'][:60]}")
134
+ assert dec_result["plaintext"] == test_data[:190].decode("utf-8")
135
+
136
+ print("\n✅ M3 complete — RSA-2048 encryptor working correctly")
@@ -0,0 +1,72 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(
7
+ description="⚡ QuantumShield Reverse Proxy — Zero-Code-Change Post-Quantum Security (ML-KEM-768)"
8
+ )
9
+ parser.add_argument(
10
+ "--port", "-p",
11
+ type=int,
12
+ default=8443,
13
+ help="Port to run the QuantumShield Reverse Proxy on (default: 8443)"
14
+ )
15
+ parser.add_argument(
16
+ "--upstream", "-u",
17
+ type=str,
18
+ default="http://localhost:3001",
19
+ help="Upstream target application URL (default: http://localhost:3001)"
20
+ )
21
+ parser.add_argument(
22
+ "--mode", "-m",
23
+ choices=["KYBER_PQC", "RSA_ONLY"],
24
+ default="KYBER_PQC",
25
+ help="Protection mode: KYBER_PQC or RSA_ONLY (default: KYBER_PQC)"
26
+ )
27
+ args = parser.parse_args()
28
+
29
+ # Interactive prompt if executed directly in a TTY terminal without explicit flags
30
+ if sys.stdin.isatty() and not os.environ.get("NON_INTERACTIVE"):
31
+ has_upstream_flag = any(a in sys.argv for a in ["--upstream", "-u"])
32
+ has_port_flag = any(a in sys.argv for a in ["--port", "-p"])
33
+ has_mode_flag = any(a in sys.argv for a in ["--mode", "-m"])
34
+
35
+ if not (has_upstream_flag and has_port_flag and has_mode_flag):
36
+ print("\n🌐 QuantumShield PQC Reverse Proxy — Setup Wizard")
37
+ print("─────────────────────────────────────────────────")
38
+ if not has_upstream_flag:
39
+ u_in = input(f"Enter target upstream app URL [default: {args.upstream}]: ").strip()
40
+ if u_in:
41
+ args.upstream = u_in
42
+ if not has_port_flag:
43
+ p_in = input(f"Enter proxy listening port [default: {args.port}]: ").strip()
44
+ if p_in and p_in.isdigit():
45
+ args.port = int(p_in)
46
+ if not has_mode_flag:
47
+ m_in = input("Select initial protection mode ([1] KYBER_PQC (Post-Quantum Safe) / [2] RSA_ONLY) [default: 1]: ").strip()
48
+ if m_in == "2" or m_in.lower() in ["rsa", "2"]:
49
+ args.mode = "RSA_ONLY"
50
+ print()
51
+
52
+ print("=" * 65)
53
+ print("⚡ QuantumShield PQC Reverse Proxy")
54
+ print(f" Listening on: http://0.0.0.0:{args.port}")
55
+ print(f" Forwarding to: {args.upstream}")
56
+ print(f" Initial Mode: {args.mode}")
57
+ print(" Standard: NIST FIPS 203 (ML-KEM-768)")
58
+ print("=" * 65)
59
+
60
+ os.environ["PROXY_PORT"] = str(args.port)
61
+ os.environ["UPSTREAM_URL"] = args.upstream
62
+ os.environ["PROXY_MODE"] = args.mode
63
+ if args.mode == "KYBER_PQC":
64
+ os.environ["PQC_ENABLED"] = "true"
65
+
66
+ from .proxy_server import proxy_app, PROXY_STATE
67
+ PROXY_STATE["mode"] = args.mode
68
+
69
+ proxy_app.run(host="0.0.0.0", port=args.port, debug=False)
70
+
71
+ if __name__ == "__main__":
72
+ main()
@@ -0,0 +1,169 @@
1
+ """
2
+ pqc_encryptor.py
3
+ ──────────────────────────────────────────────────────────────────────────────
4
+ CRYSTALS-Kyber-768 key generation, encapsulation, and decapsulation via liboqs.
5
+ NIST FIPS 203 — finalized August 2024.
6
+
7
+ KEY DIFFERENCE FROM RSA (important for viva):
8
+ ──────────────────────────────────────────────
9
+ RSA is a direct encryption scheme — you encrypt data directly with the public key.
10
+
11
+ Kyber is a Key Encapsulation Mechanism (KEM):
12
+ 1. encapsulate() → produces a random shared secret + a ciphertext
13
+ 2. decapsulate() → recovers the same shared secret from the ciphertext
14
+
15
+ The shared secret (32 bytes) is then used as a symmetric key (e.g. AES-256)
16
+ to encrypt the actual data. This is the standard hybrid encryption pattern.
17
+
18
+ Why KEM instead of direct encryption?
19
+ - KEMs are more efficient for large data
20
+ - The security proof is cleaner and stronger
21
+ - NIST chose KEM-based designs for all PQC key exchange standards
22
+
23
+ Quantum security basis:
24
+ - RSA relies on integer factorization → broken by Shor's Algorithm
25
+ - Kyber relies on Module-LWE (Learning With Errors) lattice problem
26
+ - No known quantum algorithm solves LWE in polynomial time
27
+ - Kyber-768 provides 184 bits of quantum security
28
+ ──────────────────────────────────────────────────────────────────────────────
29
+ """
30
+
31
+ import oqs
32
+ import time
33
+
34
+
35
+ class PQCEncryptor:
36
+ """CRYSTALS-Kyber-768 KEM using liboqs (Open Quantum Safe project)."""
37
+
38
+ def __init__(self, algorithm: str = "Kyber768"):
39
+ self.algorithm = algorithm
40
+ self.public_key: bytes | None = None
41
+ self.secret_key: bytes | None = None
42
+
43
+ def generate_keypair(self) -> dict:
44
+ """
45
+ Generate a Kyber-768 key pair.
46
+ Returns key gen time, key sizes, algorithm metadata.
47
+ """
48
+ start = time.perf_counter()
49
+ with oqs.KeyEncapsulation(self.algorithm) as kem:
50
+ self.public_key = kem.generate_keypair()
51
+ self.secret_key = kem.export_secret_key()
52
+ elapsed = (time.perf_counter() - start) * 1000
53
+
54
+ return {
55
+ "key_gen_time_ms": round(elapsed, 3),
56
+ "public_key_size_bytes": len(self.public_key),
57
+ "secret_key_size_bytes": len(self.secret_key),
58
+ "algorithm": self.algorithm,
59
+ "nist_standard": "FIPS 203",
60
+ "quantum_resistant": True,
61
+ "quantum_security_bits": 184,
62
+ }
63
+
64
+ def encapsulate(self) -> dict:
65
+ """
66
+ Generate a shared secret and its ciphertext using the public key.
67
+
68
+ The sender runs this — they get:
69
+ - ciphertext: sent to the other party (or intercepted by attacker)
70
+ - shared_secret: used locally as symmetric encryption key
71
+
72
+ An attacker who intercepts the ciphertext cannot recover the shared
73
+ secret without solving the Module-LWE problem.
74
+ """
75
+ if self.public_key is None:
76
+ raise RuntimeError("Call generate_keypair() before encapsulate()")
77
+
78
+ start = time.perf_counter()
79
+ with oqs.KeyEncapsulation(self.algorithm) as kem:
80
+ ciphertext, shared_secret = kem.encap_secret(self.public_key)
81
+ elapsed = (time.perf_counter() - start) * 1000
82
+
83
+ return {
84
+ "ciphertext": ciphertext.hex(),
85
+ "shared_secret": shared_secret.hex(),
86
+ "encapsulation_time_ms": round(elapsed, 3),
87
+ "ciphertext_size_bytes": len(ciphertext),
88
+ "shared_secret_size_bytes": len(shared_secret),
89
+ }
90
+
91
+ def decapsulate(self, ciphertext_hex: str) -> dict:
92
+ """
93
+ Recover the shared secret from a ciphertext using the secret key.
94
+
95
+ The receiver runs this — they get the same shared_secret the sender
96
+ computed during encapsulate(), without it ever being transmitted.
97
+ This is the core security property of a KEM.
98
+ """
99
+ if self.secret_key is None:
100
+ raise RuntimeError("Call generate_keypair() before decapsulate()")
101
+
102
+ start = time.perf_counter()
103
+ with oqs.KeyEncapsulation(self.algorithm, secret_key=self.secret_key) as kem:
104
+ shared_secret = kem.decap_secret(bytes.fromhex(ciphertext_hex))
105
+ elapsed = (time.perf_counter() - start) * 1000
106
+
107
+ return {
108
+ "shared_secret": shared_secret.hex(),
109
+ "decapsulation_time_ms": round(elapsed, 3),
110
+ }
111
+
112
+
113
+ # ─── Self-test when run directly ──────────────────────────────────────────────
114
+
115
+ if __name__ == "__main__":
116
+ import os
117
+ os.environ["LIBOQS_PYTHON_FAULTHANDLER"] = "0" # suppress the faulthandler info message
118
+
119
+ enc = PQCEncryptor()
120
+
121
+ print("=" * 60)
122
+ print(" CRYSTALS-Kyber-768 — Post-Quantum KEM Demo")
123
+ print(" NIST FIPS 203 | Quantum Security: 184 bits")
124
+ print("=" * 60)
125
+
126
+ print("\n🔑 Step 1: Key Generation")
127
+ keygen = enc.generate_keypair()
128
+ print(f" Algorithm : {keygen['algorithm']}")
129
+ print(f" NIST standard : {keygen['nist_standard']} (finalized Aug 2024)")
130
+ print(f" Key gen time : {keygen['key_gen_time_ms']} ms")
131
+ print(f" Public key size : {keygen['public_key_size_bytes']} bytes")
132
+ print(f" Secret key size : {keygen['secret_key_size_bytes']} bytes")
133
+ print(f" Quantum security : {keygen['quantum_security_bits']} bits ✅")
134
+ print(f" Quantum safe : {keygen['quantum_resistant']} ← Shor's Algorithm CANNOT break this")
135
+ assert keygen["public_key_size_bytes"] == 1184
136
+ assert keygen["quantum_resistant"] is True
137
+
138
+ print("\n🔒 Step 2: Encapsulation (Sender side)")
139
+ print(" What happens: sender generates a random shared secret")
140
+ print(" + a ciphertext that only the receiver can open")
141
+ encap = enc.encapsulate()
142
+ print(f" Encap time : {encap['encapsulation_time_ms']} ms")
143
+ print(f" Ciphertext size : {encap['ciphertext_size_bytes']} bytes (sent over network)")
144
+ print(f" Shared secret (hex) : {encap['shared_secret'][:40]}...")
145
+ print(f" Shared secret size : {encap['shared_secret_size_bytes']} bytes = AES-256 key")
146
+ print(f" ⚠️ If attacker intercepts ciphertext → they CANNOT recover shared secret")
147
+ print(f" ⚠️ Shor's Algorithm fails — no periodic structure in LWE lattice")
148
+ assert encap["ciphertext_size_bytes"] == 1088
149
+
150
+ print("\n🔓 Step 3: Decapsulation (Receiver side)")
151
+ print(" What happens: receiver uses secret key to recover the SAME shared secret")
152
+ print(" The shared secret was NEVER transmitted — only the ciphertext was")
153
+ decap = enc.decapsulate(encap["ciphertext"])
154
+ print(f" Decap time : {decap['decapsulation_time_ms']} ms")
155
+ print(f" Recovered secret : {decap['shared_secret'][:40]}...")
156
+
157
+ match = encap["shared_secret"] == decap["shared_secret"]
158
+ print(f"\n{'✅' if match else '❌'} Secrets match: {match}")
159
+ print(f" → Both parties now share the same 32-byte key")
160
+ print(f" → They use it for AES-256 to encrypt actual transaction data")
161
+ print(f" → A quantum attacker intercepting the ciphertext gets NOTHING")
162
+
163
+ assert match, "Shared secrets do not match — decapsulation failed!"
164
+
165
+ print("\n" + "=" * 60)
166
+ print(" VERDICT: QUANTUM SAFE ✅")
167
+ print(" RSA-2048 would be broken here. Kyber-768 is not.")
168
+ print("=" * 60)
169
+ print("\n✅ M4 complete — CRYSTALS-Kyber-768 encryptor working correctly")
@@ -0,0 +1,378 @@
1
+ """
2
+ proxy_server.py — QuantumShield Reverse Proxy (Port 8443)
3
+ ──────────────────────────────────────────────────────────────────────────────
4
+ Architecture:
5
+ Browser (ShopEasy checkout)
6
+ │ RSA-encrypted payload (browser pre-encrypted with SubtleCrypto)
7
+
8
+ POST http://localhost:8443/proxy/pay ← This server
9
+ │ 1. Log intercepted ciphertext to Harvest Vault
10
+ │ 2. If KYBER_PQC mode: also wrap in Kyber-768 KEM envelope
11
+ │ 3. Forward to merchant: http://localhost:3001/api/pay
12
+
13
+ POST http://localhost:3001/api/pay ← ShopEasy merchant (internal)
14
+ │ Returns order_id, rsa_config (p, q, N)
15
+
16
+ Proxy returns tx_hash + order_id to browser
17
+
18
+ Enabling PQC protection (single line):
19
+ Via /migrate UI: POST /api/proxy/activate { "mode": "KYBER_PQC" }
20
+ Via env var: PQC_ENABLED=true (read at startup, sets mode)
21
+
22
+ Disabling:
23
+ POST /api/proxy/activate { "mode": "RSA_ONLY" }
24
+ or PQC_ENABLED=false and restart
25
+ ──────────────────────────────────────────────────────────────────────────────
26
+ """
27
+
28
+ import os
29
+ import time
30
+ import json
31
+ import hashlib
32
+ import urllib.request
33
+ from flask import Flask, request, jsonify
34
+ from flask_cors import CORS
35
+ from .pqc_encryptor import PQCEncryptor
36
+ from .classical_encryptor import ClassicalEncryptor
37
+
38
+ proxy_app = Flask(__name__)
39
+ CORS(proxy_app, resources={r"/*": {"origins": "*"}})
40
+
41
+ # ── Proxy state ───────────────────────────────────────────────────────────────
42
+ # mode: "KYBER_PQC" → wrap RSA ciphertext in Kyber-768, forward (quantum SAFE) [DEFAULT]
43
+ # "RSA_ONLY" → log RSA ciphertext, forward as-is (quantum VULNERABLE)
44
+
45
+ def get_upstream_pay_url():
46
+ base_url = os.environ.get("UPSTREAM_URL", "http://localhost:3001").rstrip("/")
47
+ if not base_url.startswith("http://") and not base_url.startswith("https://"):
48
+ base_url = "http://" + base_url
49
+ if base_url.endswith("/api/pay"):
50
+ return base_url
51
+ return f"{base_url}/api/pay"
52
+
53
+
54
+ _initial_mode = "RSA_ONLY" if os.environ.get("PQC_ENABLED", "true").lower() == "false" else "KYBER_PQC"
55
+
56
+ PROXY_STATE = {
57
+ "mode": _initial_mode,
58
+ "active_since": time.strftime("%Y-%m-%d %H:%M:%S"),
59
+ }
60
+
61
+ print(f"[Proxy] Starting in mode: {PROXY_STATE['mode']}")
62
+
63
+ HARVEST_VAULT = [] # Stores all intercepted transactions
64
+ INTERCEPTION_LOG = [] # Lightweight log for /api/proxy/log
65
+
66
+ # ── Crypto engines ────────────────────────────────────────────────────────────
67
+ # ML-KEM-768 = NIST FIPS 203 standard (finalized Aug 2024)
68
+ # This matches what @noble/post-quantum ml_kem768 implements in the browser,
69
+ # so browser encapsulate() ↔ proxy decapsulate() are wire-compatible.
70
+ pqc_engine = PQCEncryptor(algorithm="ML-KEM-768")
71
+ pqc_engine.generate_keypair()
72
+
73
+ classical_engine = ClassicalEncryptor()
74
+
75
+
76
+ # ── XOR decrypt helper (mirrors browser xorEncrypt) ─────────────────────────
77
+
78
+ def xor_decrypt(hex_str: str, k: int) -> str:
79
+ """Reverse of browser xorEncrypt(str, k): each byte b XOR ((k*(i+1)) % 256)."""
80
+ data = bytes.fromhex(hex_str)
81
+ out = bytes(b ^ ((k * (i + 1)) % 256) for i, b in enumerate(data))
82
+ return out.decode("utf-8", errors="replace")
83
+
84
+
85
+ def xor_decrypt_with_secret(hex_str: str, secret_hex: str) -> str:
86
+ """XOR-decrypt payload using Kyber shared secret bytes as repeating key."""
87
+ data = bytes.fromhex(hex_str)
88
+ key = bytes.fromhex(secret_hex)
89
+ out = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
90
+ return out.decode("utf-8", errors="replace")
91
+
92
+
93
+ # ── Vault helper ──────────────────────────────────────────────────────────────
94
+
95
+ def add_to_vault(payload: dict, merchant_response: dict, is_pqc: bool) -> dict:
96
+ tx_id = f"TX-{len(HARVEST_VAULT) + 1001}"
97
+ raw_str = json.dumps(payload) + json.dumps(merchant_response)
98
+ tx_hash = "0x" + hashlib.sha256(raw_str.encode()).hexdigest()
99
+
100
+ method = payload.get("method", "card")
101
+ amount = payload.get("amount", 0)
102
+ payment_summary = payload.get("payment_summary", "")
103
+ rsa_config = payload.get("rsa_config", merchant_response.get("rsa_config", {}))
104
+
105
+ # The actual ciphertext for Shor's to decrypt:
106
+ # rsa_encrypted_key = c = k^e mod N (RSA-encrypted session key)
107
+ # xor_encrypted_payload = payload XOR seed(k) (the encrypted payment JSON)
108
+ rsa_encrypted_key = payload.get("rsa_encrypted_key", "")
109
+ xor_encrypted_payload = payload.get("xor_encrypted_payload", "")
110
+
111
+ record = {
112
+ "id": tx_id,
113
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
114
+ "merchant": merchant_response.get("merchant", "ShopEasy Store"),
115
+ "amount": amount,
116
+ "method": method,
117
+ "payment_summary": payment_summary,
118
+ "mode": "KYBER-768 (PQC)" if is_pqc else "RSA-2048 (VULNERABLE)",
119
+ "status": "PROTECTED" if is_pqc else "EXPOSED",
120
+ "tx_hash": tx_hash,
121
+ # raw_payload: everything the attack page needs to run actual decryption
122
+ "raw_payload": {
123
+ "rsa_encrypted_key": rsa_encrypted_key, # c = k^e mod N
124
+ "xor_encrypted_payload": xor_encrypted_payload, # XOR ciphertext
125
+ "rsa_config": rsa_config,
126
+ "amount": amount,
127
+ "method": method,
128
+ },
129
+ # Kyber ciphertext (populated in KYBER mode)
130
+ "kyber_ciphertext": payload.get("kyber_ciphertext", ""),
131
+ }
132
+
133
+ HARVEST_VAULT.insert(0, record)
134
+ INTERCEPTION_LOG.insert(0, {
135
+ "timestamp": record["timestamp"],
136
+ "tx_id": tx_id,
137
+ "mode": record["mode"],
138
+ "status": record["status"],
139
+ "amount": f"₹{amount}",
140
+ })
141
+
142
+ if len(HARVEST_VAULT) > 100:
143
+ HARVEST_VAULT.pop()
144
+ if len(INTERCEPTION_LOG) > 100:
145
+ INTERCEPTION_LOG.pop()
146
+
147
+ return record
148
+
149
+
150
+ # ── Main proxy endpoint ───────────────────────────────────────────────────────
151
+
152
+ @proxy_app.route("/proxy/pay", methods=["POST", "OPTIONS"])
153
+ def proxy_payment():
154
+ if request.method == "OPTIONS":
155
+ return jsonify({"status": "OK"}), 200
156
+
157
+ data = request.get_json(force=True)
158
+ is_pqc = (PROXY_STATE["mode"] == "KYBER_PQC")
159
+
160
+ # ── 1. Forward to ShopEasy merchant (3001/api/pay) ────────────────────────
161
+ # The encrypted_hex was RSA-encrypted by the browser using SubtleCrypto.
162
+ # We forward it to the merchant so they can assign an order_id and return
163
+ # the rsa_config.N (which the attack page needs to run Shor's algorithm).
164
+ forward_body = json.dumps(data).encode("utf-8")
165
+ merchant_req = urllib.request.Request(
166
+ get_upstream_pay_url(),
167
+ data=forward_body,
168
+ headers={
169
+ "Content-Type": "application/json",
170
+ "User-Agent": "QuantumShield-Proxy/1.0",
171
+ },
172
+ method="POST",
173
+ )
174
+ with urllib.request.urlopen(merchant_req, timeout=5) as resp:
175
+ merchant_response = json.loads(resp.read().decode("utf-8"))
176
+
177
+ # Update rsa_config from merchant (authoritative source)
178
+ if "rsa_config" in merchant_response:
179
+ data["rsa_config"] = merchant_response["rsa_config"]
180
+
181
+ # ── 2. Log intercepted ciphertext to Harvest Vault ────────────────────────
182
+ record = add_to_vault(data, merchant_response, is_pqc)
183
+
184
+ # ── 3. Apply Kyber-768 wrapper if PQC mode is active ─────────────────────
185
+ kyber_ciphertext = None
186
+ if is_pqc:
187
+ encap_res = pqc_engine.encapsulate()
188
+ kyber_ciphertext = encap_res.get("ciphertext")
189
+
190
+ # ── 4. Return response to browser ─────────────────────────────────────────
191
+ response_payload = {
192
+ "status": "SUCCESS",
193
+ "order_id": merchant_response.get("order_id", record["id"]),
194
+ "amount": merchant_response.get("amount", data.get("amount")),
195
+ "merchant": "ShopEasy Store",
196
+ "pqc_secured": is_pqc and kyber_ciphertext is not None,
197
+ "mode": record["mode"],
198
+ "tx_hash": record["tx_hash"],
199
+ "rsa_config": data.get("rsa_config", {}),
200
+ "kyber_ciphertext": (kyber_ciphertext[:64] + "...") if kyber_ciphertext else None,
201
+ }
202
+
203
+ return jsonify(response_payload), 200
204
+
205
+
206
+ # ── Kyber-encrypted payment endpoint ─────────────────────────────────────────
207
+
208
+ @proxy_app.route("/api/proxy/pubkey", methods=["GET"])
209
+ def get_kyber_pubkey():
210
+ """Return the proxy's ML-KEM-768 public key as hex. Browser uses this to encapsulate."""
211
+ if pqc_engine.public_key is None:
212
+ return jsonify({"error": "Keypair not generated"}), 500
213
+ return jsonify({
214
+ "public_key_hex": pqc_engine.public_key.hex(),
215
+ "algorithm": pqc_engine.algorithm, # "ML-KEM-768" — matches @noble/post-quantum ml_kem768
216
+ "public_key_size_bytes": len(pqc_engine.public_key),
217
+ "note": "Use this public key with ml_kem768.encapsulate(). Send kyber_ciphertext to /api/proxy/pay/kyber",
218
+ })
219
+
220
+
221
+ @proxy_app.route("/api/proxy/pay/kyber", methods=["POST", "OPTIONS"])
222
+ def proxy_payment_kyber():
223
+ """
224
+ Accepts a Kyber-KEM-encrypted payment from the browser.
225
+
226
+ Browser sends:
227
+ {
228
+ kyber_ciphertext: <hex> — KEM ciphertext (encapsulated shared secret)
229
+ kyber_encrypted_payload: <hex> — payload XOR-encrypted with shared secret
230
+ amount: <int>
231
+ method: <str>
232
+ payment_summary: <str>
233
+ }
234
+
235
+ This proxy:
236
+ 1. Decapsulates kyber_ciphertext with the secret key → recovers shared_secret
237
+ 2. XOR-decrypts kyber_encrypted_payload with shared_secret → plaintext JSON
238
+ 3. Forwards to merchant (3001/api/pay) for order_id
239
+ 4. Stores the Kyber ciphertext (NOT the plaintext) in the vault
240
+ """
241
+ if request.method == "OPTIONS":
242
+ return jsonify({"status": "OK"}), 200
243
+
244
+ data = request.get_json(force=True) or {}
245
+
246
+ kyber_ct_hex = data.get("kyber_ciphertext", "")
247
+ kyber_payload_hex = data.get("kyber_encrypted_payload", "")
248
+
249
+ # ── 1. Decapsulate to recover shared secret ───────────────────────────────
250
+ try:
251
+ decap_result = pqc_engine.decapsulate(kyber_ct_hex)
252
+ shared_secret_hex = decap_result["shared_secret"]
253
+ except Exception as ex:
254
+ return jsonify({"error": f"Kyber decapsulation failed: {ex}"}), 400
255
+
256
+ # ── 2. XOR-decrypt the payload with the shared secret ────────────────────
257
+ try:
258
+ plaintext = xor_decrypt_with_secret(kyber_payload_hex, shared_secret_hex)
259
+ payment_data = json.loads(plaintext)
260
+ except Exception as ex:
261
+ return jsonify({"error": f"Payload decryption failed: {ex}"}), 400
262
+
263
+ # ── 3. Forward to ShopEasy merchant ──────────────────────────────────────
264
+ forward_body = json.dumps(payment_data).encode("utf-8")
265
+ try:
266
+ merchant_req = urllib.request.Request(
267
+ get_upstream_pay_url(),
268
+ data=forward_body,
269
+ headers={"Content-Type": "application/json", "User-Agent": "QuantumShield-Proxy/1.0"},
270
+ method="POST",
271
+ )
272
+ with urllib.request.urlopen(merchant_req, timeout=5) as resp:
273
+ merchant_response = json.loads(resp.read().decode("utf-8"))
274
+ except Exception as ex:
275
+ merchant_response = {"order_id": f"SE-K{len(HARVEST_VAULT)+1001}", "merchant": "ShopEasy Store"}
276
+
277
+ # ── 4. Store KYBER ciphertext in vault (not plaintext!) ───────────────────
278
+ vault_payload = {
279
+ "amount": data.get("amount", payment_data.get("amount", 0)),
280
+ "method": data.get("method", payment_data.get("method", "card")),
281
+ "payment_summary": data.get("payment_summary", ""),
282
+ "kyber_ciphertext": kyber_ct_hex, # what Wireshark sees on wire
283
+ "kyber_encrypted_payload": kyber_payload_hex,
284
+ # NO rsa_encrypted_key — Shor's has nothing to work with
285
+ "rsa_encrypted_key": "",
286
+ "xor_encrypted_payload": "",
287
+ "rsa_config": {},
288
+ }
289
+ record = add_to_vault(vault_payload, merchant_response, is_pqc=True)
290
+
291
+ return jsonify({
292
+ "status": "SUCCESS",
293
+ "order_id": merchant_response.get("order_id", record["id"]),
294
+ "amount": vault_payload["amount"],
295
+ "merchant": "ShopEasy Store",
296
+ "pqc_secured": True,
297
+ "mode": "KYBER-768 (PQC)",
298
+ "tx_hash": record["tx_hash"],
299
+ "kyber_ciphertext_preview": kyber_ct_hex[:64] + "...",
300
+ }), 200
301
+
302
+
303
+ # ── Control endpoints ─────────────────────────────────────────────────────────
304
+
305
+ @proxy_app.route("/api/proxy/activate", methods=["POST", "OPTIONS"])
306
+ def activate_proxy():
307
+ """Toggle protection mode. Called by /migrate UI."""
308
+ if request.method == "OPTIONS":
309
+ return jsonify({"status": "OK"}), 200
310
+
311
+ body = request.get_json(force=True) or {}
312
+ mode = body.get("mode", "KYBER_PQC")
313
+ if mode not in ("RSA_ONLY", "KYBER_PQC"):
314
+ return jsonify({"status": "ERROR", "message": "mode must be RSA_ONLY or KYBER_PQC"}), 400
315
+
316
+ PROXY_STATE["mode"] = mode
317
+ PROXY_STATE["active_since"] = time.strftime("%Y-%m-%d %H:%M:%S")
318
+
319
+ return jsonify({
320
+ "status": "SUCCESS",
321
+ "mode": PROXY_STATE["mode"],
322
+ "active_since": PROXY_STATE["active_since"],
323
+ "message": f"Proxy switched to {mode}.",
324
+ })
325
+
326
+
327
+ @proxy_app.route("/api/proxy/status", methods=["GET"])
328
+ def proxy_status():
329
+ return jsonify({
330
+ "service": "QuantumShield PQC Reverse Proxy",
331
+ "port": 8443,
332
+ "mode": PROXY_STATE["mode"],
333
+ "pqc_active": PROXY_STATE["mode"] == "KYBER_PQC",
334
+ "harvested_count": len(HARVEST_VAULT),
335
+ "active_since": PROXY_STATE["active_since"],
336
+ })
337
+
338
+
339
+ @proxy_app.route("/api/proxy/vault", methods=["GET"])
340
+ def get_vault():
341
+ return jsonify({
342
+ "total_harvested": len(HARVEST_VAULT),
343
+ "vault": HARVEST_VAULT,
344
+ })
345
+
346
+
347
+ @proxy_app.route("/api/proxy/log", methods=["GET"])
348
+ def get_log():
349
+ return jsonify({
350
+ "mode": PROXY_STATE["mode"],
351
+ "logs": INTERCEPTION_LOG,
352
+ })
353
+
354
+
355
+ @proxy_app.route("/api/proxy/vault/clear", methods=["POST", "OPTIONS"])
356
+ def clear_vault():
357
+ """Clear all harvested records — useful before a demo run."""
358
+ if request.method == "OPTIONS":
359
+ return jsonify({"status": "OK"}), 200
360
+ HARVEST_VAULT.clear()
361
+ INTERCEPTION_LOG.clear()
362
+ return jsonify({"status": "CLEARED", "message": "Vault and interception log reset."})
363
+
364
+
365
+ # ── Entry point ───────────────────────────────────────────────────────────────
366
+
367
+ if __name__ == "__main__":
368
+ port = int(os.environ.get("PROXY_PORT", "8443"))
369
+ upstream = get_upstream_pay_url()
370
+ print("=" * 65)
371
+ print("⚡ QuantumShield PQC Reverse Proxy")
372
+ print(f" Mode: {PROXY_STATE['mode']}")
373
+ print(f" Listening on: http://0.0.0.0:{port}")
374
+ print(f" Forwarding to: {upstream}")
375
+ print(" Standard: NIST FIPS 203 (ML-KEM-768)")
376
+ print("=" * 65)
377
+ print()
378
+ proxy_app.run(host="0.0.0.0", port=port, debug=False)
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: quantumshield-proxy
3
+ Version: 1.0.0
4
+ Summary: Zero-code-change Post-Quantum Cryptography (ML-KEM-768 / Kyber) Reverse Proxy
5
+ Author-email: Hemanth <dev@quantumshield.io>
6
+ Project-URL: Homepage, https://github.com/hemanth/Ccp_QuantumShield
7
+ Project-URL: Bug Tracker, https://github.com/hemanth/Ccp_QuantumShield/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Topic :: Security :: Cryptography
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: flask>=3.0.0
15
+ Requires-Dist: flask-cors>=4.0.0
16
+ Requires-Dist: liboqs-python>=0.10.0
17
+ Requires-Dist: cryptography>=42.0.0
18
+ Requires-Dist: requests>=2.31.0
19
+
20
+ # QuantumShield — Backend
21
+
22
+ Python Flask backend for the QuantumShield post-quantum cryptography demo.
23
+
24
+ ## Structure
25
+
26
+ ```
27
+ backend/
28
+ ├── app.py # Flask entry point — all routes
29
+ ├── classical_encryptor.py # RSA-2048 encrypt/decrypt + timing
30
+ ├── pqc_encryptor.py # CRYSTALS-Kyber-768 via liboqs + timing
31
+ ├── quantum_simulator.py # Shor's Algorithm simulation
32
+ ├── benchmark_engine.py # 1000 transaction benchmarking
33
+ ├── vulnerability_scanner.py # Algorithm risk classification
34
+ ├── migration_wizard.py # 5-step migration plan + PDF export
35
+ ├── transaction_factory.py # Synthetic dataset loader
36
+ ├── data/
37
+ │ └── mock_transactions.json # 1000 synthetic UPI transactions
38
+ ├── requirements.txt
39
+ ├── .env.example
40
+ └── .gitignore
41
+ ```
42
+
43
+ ## Setup
44
+
45
+ ```bash
46
+ pip install -r requirements.txt
47
+ ```
48
+
49
+ ## Run
50
+
51
+ ```bash
52
+ python app.py
53
+ # Flask running on http://localhost:5000
54
+ ```
55
+
56
+ ## API Endpoints
57
+
58
+ | Method | Endpoint | Description |
59
+ |--------|-----------------------|------------------------------------|
60
+ | GET | /api/health | Health check |
61
+ | POST | /api/encrypt/rsa | Encrypt with RSA-2048 |
62
+ | POST | /api/encrypt/kyber | Encapsulate with Kyber-768 |
63
+ | POST | /api/gateway/pay | Full payment gateway simulation |
64
+ | GET | /api/attack/rsa | Shor's attack on RSA |
65
+ | GET | /api/attack/kyber | Shor's attack on Kyber |
66
+ | GET | /api/benchmark?n=100 | Run N transaction benchmark |
67
+ | POST | /api/scan | Vulnerability scan an algorithm |
68
+ | POST | /api/migrate | Generate 5-step migration plan |
69
+ | POST | /api/report | Download PDF migration report |
70
+
71
+ ## Test individually
72
+
73
+ ```bash
74
+ python transaction_factory.py # M1
75
+ python classical_encryptor.py # M3
76
+ python pqc_encryptor.py # M4
77
+ python quantum_simulator.py # M5
78
+ python vulnerability_scanner.py # M8a
79
+ python migration_wizard.py # M8b
80
+ python benchmark_engine.py # M9
81
+ ```
@@ -0,0 +1,11 @@
1
+ quantumshield_proxy/__init__.py,sha256=8BkCJKv73pUQZijA98t6SkABhunMr8u8xfMH9OHCITA,279
2
+ quantumshield_proxy/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
+ quantumshield_proxy/classical_encryptor.py,sha256=9gH5fr5Hr3Tk5e8ZT5PtrML7x1PifJpaJcVvQfW8JBQ,5606
4
+ quantumshield_proxy/cli.py,sha256=rDOb04VyHwkvY2IL9MFD62K6vjC6k81_1w3pFkcMWI4,2910
5
+ quantumshield_proxy/pqc_encryptor.py,sha256=DFCE-VtsBsN5FQIkAoP7eCWVBF12FuOpx6eWaGQTCgA,7852
6
+ quantumshield_proxy/proxy_server.py,sha256=FbPxokDegbt1tE1wyiON5H8pwOdKbge4oh4Ztj_qKtQ,16277
7
+ quantumshield_proxy-1.0.0.dist-info/METADATA,sha256=0brXcaZT9RD3NH1vOevg6s07nE3hYaup6velKpoeaiI,2928
8
+ quantumshield_proxy-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
+ quantumshield_proxy-1.0.0.dist-info/entry_points.txt,sha256=BTkgS7J1SA86KDcp9VJ2MdKUOIlrxx6HDIwfwVU5fLs,69
10
+ quantumshield_proxy-1.0.0.dist-info/top_level.txt,sha256=YP8Z5S5N7XUkZ4opIkRgwtqmjy4ePL7_BGdOMOtvEUI,20
11
+ quantumshield_proxy-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ quantumshield-proxy = quantumshield_proxy.cli:main
@@ -0,0 +1 @@
1
+ quantumshield_proxy