diff --git a/IS/Lab/Eval-Endsem/ans.py b/IS/Lab/Eval-Endsem/ans.py new file mode 100644 index 0000000..4b7a362 --- /dev/null +++ b/IS/Lab/Eval-Endsem/ans.py @@ -0,0 +1,434 @@ + +import os +import json +import base64 +import uuid +from datetime import datetime, timezone + +from Crypto.PublicKey import RSA, ElGamal +from Crypto.Cipher import PKCS1_OAEP +from Crypto.Hash import MD5 +from Crypto.Random import random, get_random_bytes +from Crypto.Util.number import GCD, inverse + +try: + from phe import paillier +except ImportError: + print("Install dependency: pip install phe") + raise + +STATE_FILE = "server_state.json" +INPUT_DIR = "inputdata" + +def ensure_dirs(): + if not os.path.exists(INPUT_DIR): + os.makedirs(INPUT_DIR) + +def load_state(): + if not os.path.exists(STATE_FILE): + return { + "server": {}, + "doctors": {}, + "reports": [], + "expenses": {} + } + with open(STATE_FILE, "r") as f: + return json.load(f) + +def save_state(state): + with open(STATE_FILE, "w") as f: + json.dump(state, f, indent=2) + +def gen_server_keys(state): + if "rsa_oaep" in state["server"]: + print("Server keys already exist.") + return + rsa_oaep_key = RSA.generate(512) + pub_pem = rsa_oaep_key.publickey().export_key().decode() + priv_pem = rsa_oaep_key.export_key().decode() + + homo_rsa = RSA.generate(512) + n = int(homo_rsa.n) + e = int(homo_rsa.e) + d = int(homo_rsa.d) + # base for exponent-trick homomorphic addition + while True: + base = random.randint(2, n - 2) + if GCD(base, n) == 1: + break + max_exp = 10000 + + pub, priv = paillier.generate_paillier_keypair(n_length=1024) + + state["server"]["rsa_oaep"] = {"pub_pem": pub_pem, "priv_pem": priv_pem} + state["server"]["homo_rsa"] = {"n": n, "e": e, "d": d, "base": base, "max_exp": max_exp} + state["server"]["paillier"] = {"n": pub.n, "p": priv.p, "q": priv.q} + save_state(state) + print("Server RSA-OAEP, Homo-RSA, and Paillier keys generated.") + +def get_paillier_keys(state): + n = state["server"]["paillier"]["n"] + p = state["server"]["paillier"]["p"] + q = state["server"]["paillier"]["q"] + pub = paillier.PaillierPublicKey(n) + priv = paillier.PaillierPrivateKey(pub, p, q) + return pub, priv + +def register_doctor(state): + name = input("Doctor name: ").strip() + dept = input("Department: ").strip() + doc_id = "doc_" + uuid.uuid4().hex[:8] + + eg_key = ElGamal.generate(512, get_random_bytes) + # ElGamal object has p,g,y,x attributes + p = int(eg_key.p) + g = int(eg_key.g) + y = int(eg_key.y) + x = int(eg_key.x) + + # Paillier encrypt department hash + pub, _ = get_paillier_keys(state) + dept_md5_int = int.from_bytes(MD5.new(dept.encode()).digest(), "big") + dept_enc = pub.encrypt(dept_md5_int) + + state["doctors"][doc_id] = { + "name": name, + "department": dept, + "department_md5": dept_md5_int, + "department_paillier": { + "ciphertext": int(dept_enc.ciphertext()), + "exponent": dept_enc.exponent + }, + "elgamal": {"p": p, "g": g, "y": y, "x": x} + } + save_state(state) + print(f"Registered doctor {name} with id {doc_id} in dept {dept}.") + print(f"ElGamal pub (p,g,y) set. Department stored both plaintext and Paillier-encrypted.") + +def list_markdown_files(): + ensure_dirs() + files = [f for f in os.listdir(INPUT_DIR) if f.lower().endswith(".md")] + for i, f in enumerate(files, 1): + print(f"{i}. {f}") + return files + +def rsa_oaep_encrypt_large(data_bytes, pub_pem): + pub = RSA.import_key(pub_pem) + cipher = PKCS1_OAEP.new(pub) # SHA1 by default + k = pub.size_in_bytes() + hlen = 20 + max_pt = k - 2*hlen - 2 + out = b"" + for i in range(0, len(data_bytes), max_pt): + block = data_bytes[i:i+max_pt] + out += cipher.encrypt(block) + return base64.b64encode(out).decode() + +def rsa_oaep_decrypt_large(b64, priv_pem): + data = base64.b64decode(b64.encode()) + priv = RSA.import_key(priv_pem) + cipher = PKCS1_OAEP.new(priv) + k = priv.size_in_bytes() + out = b"" + for i in range(0, len(data), k): + block = data[i:i+k] + out += cipher.decrypt(block) + return out + +def elgamal_sign(doc_eg, msg_bytes): + p = int(doc_eg["p"]) + g = int(doc_eg["g"]) + x = int(doc_eg["x"]) + H = int(MD5.new(msg_bytes).hexdigest(), 16) % (p - 1) + while True: + k = random.randint(2, p - 2) + if GCD(k, p - 1) == 1: + break + r = pow(g, k, p) + kinv = inverse(k, p - 1) + s = (kinv * (H - x * r)) % (p - 1) + return int(r), int(s) + +def elgamal_verify(pub_eg, msg_bytes, sig): + p = int(pub_eg["p"]) + g = int(pub_eg["g"]) + y = int(pub_eg["y"]) + r, s = sig + if not (1 < r < p): + return False + H = int(MD5.new(msg_bytes).hexdigest(), 16) % (p - 1) + v1 = (pow(y, r, p) * pow(r, s, p)) % p + v2 = pow(g, H, p) + return v1 == v2 + +def doctor_submit_report(state): + if not state["doctors"]: + print("No doctors. Register first.") + return + doc_id = input("Enter your doctor id: ").strip() + if doc_id not in state["doctors"]: + print("Unknown doctor.") + return + files = list_markdown_files() + if not files: + print("Place a markdown file in inputdata/") + return + idx = int(input("Select file #: ").strip()) + filename = files[idx - 1] + path = os.path.join(INPUT_DIR, filename) + with open(path, "rb") as f: + report_bytes = f.read() + md5_hex = MD5.new(report_bytes).hexdigest() + ts = datetime.now(timezone.utc).isoformat() + + msg_to_sign = report_bytes + ts.encode() + r, s = elgamal_sign(state["doctors"][doc_id]["elgamal"], msg_to_sign) + + pub_pem = state["server"]["rsa_oaep"]["pub_pem"] + ct_b64 = rsa_oaep_encrypt_large(report_bytes, pub_pem) + + rep_id = "rep_" + uuid.uuid4().hex[:8] + rec = { + "report_id": rep_id, + "doctor_id": doc_id, + "doctor_name": state["doctors"][doc_id]["name"], + "filename": filename, + "timestamp_utc": ts, + "md5_hex": md5_hex, + "elgamal_sig": {"r": r, "s": s}, + "rsa_oaep_b64": ct_b64 + } + state["reports"].append(rec) + save_state(state) + print("Report submitted.") + print(f"id: {rep_id}") + print(f"md5: {md5_hex}") + print(f"sig: r={r} s={s}") + print(f"enc blocks (base64 len): {len(ct_b64)}") + +def homo_rsa_encrypt_amount(state, amount): + n = int(state["server"]["homo_rsa"]["n"]) + e = int(state["server"]["homo_rsa"]["e"]) + base = int(state["server"]["homo_rsa"]["base"]) + max_exp = int(state["server"]["homo_rsa"]["max_exp"]) + if amount < 0 or amount > max_exp: + raise ValueError("amount out of allowed range") + m = pow(base, amount, n) + c = pow(m, e, n) + return int(c) + +def homo_rsa_discrete_log(m, base, mod, max_k): + val = 1 % mod + if m == 1 % mod: + return 0 + for k in range(1, max_k + 1): + val = (val * base) % mod + if val == m: + return k + return None + +def doctor_submit_expense(state): + if not state["doctors"]: + print("No doctors. Register first.") + return + doc_id = input("Enter your doctor id: ").strip() + if doc_id not in state["doctors"]: + print("Unknown doctor.") + return + amt = int(input("Expense integer (<=10000): ").strip()) + c = homo_rsa_encrypt_amount(state, amt) + state["expenses"].setdefault(doc_id, []).append(c) + save_state(state) + print(f"Encrypted expense stored for {doc_id}.") + print(f"ciphertext: {c}") + +def auditor_list_reports(state): + if not state["reports"]: + print("No reports.") + return + for r in state["reports"]: + print(f"{r['report_id']} by {r['doctor_id']} at {r['timestamp_utc']} file={r['filename']} md5={r['md5_hex']}") + +def auditor_verify_report(state): + if not state["reports"]: + print("No reports.") + return + rep_id = input("Report id: ").strip() + rec = next((r for r in state["reports"] if r["report_id"] == rep_id), None) + if not rec: + print("Not found.") + return + priv_pem = state["server"]["rsa_oaep"]["priv_pem"] + pt = rsa_oaep_decrypt_large(rec["rsa_oaep_b64"], priv_pem) + md5_calc = MD5.new(pt).hexdigest() + ok_md5 = (md5_calc == rec["md5_hex"]) + + doc = state["doctors"][rec["doctor_id"]] + pub_eg = {"p": doc["elgamal"]["p"], "g": doc["elgamal"]["g"], "y": doc["elgamal"]["y"]} + msg = pt + rec["timestamp_utc"].encode() + ok_sig = elgamal_verify(pub_eg, msg, (rec["elgamal_sig"]["r"], rec["elgamal_sig"]["s"])) + + ts = datetime.fromisoformat(rec["timestamp_utc"]) + now = datetime.now(timezone.utc) + skew_sec = (now - ts).total_seconds() + + print("Verification results:") + print(f"md5 match: {ok_md5}") + print(f"signature valid: {ok_sig}") + print(f"timestamp: {rec['timestamp_utc']}") + print(f"server now: {now.isoformat()}") + print(f"age seconds: {int(skew_sec)} (future? {skew_sec < 0})") + +def auditor_keyword_search(state): + if not state["doctors"]: + print("No doctors.") + return + dept_q = input("Search department: ").strip() + pub, priv = get_paillier_keys(state) + q_int = int.from_bytes(MD5.new(dept_q.encode()).digest(), "big") + q_enc = pub.encrypt(q_int) + + print("Records:") + found = [] + for doc_id, doc in state["doctors"].items(): + enc_info = doc["department_paillier"] + enc_doc = paillier.EncryptedNumber(pub, int(enc_info["ciphertext"]), int(enc_info["exponent"])) + diff = enc_doc - q_enc + val = priv.decrypt(diff) + is_match = (val == 0) + if is_match: + found.append(doc_id) + print(f"{doc_id}: dept='{doc['department']}' enc_ct={enc_info['ciphertext']} match={is_match}") + print(f"Matches: {found}") + +def auditor_sum_expenses(state): + homo = state["server"]["homo_rsa"] + n = int(homo["n"]); d = int(homo["d"]); base = int(homo["base"]); max_exp = int(homo["max_exp"]) + if not state["expenses"]: + print("No expenses.") + return + choice = input("Sum for 'all' or specific doc_id: ").strip() + c_list = [] + if choice.lower() == "all": + for doc_id, lst in state["expenses"].items(): + c_list += lst + else: + if choice not in state["expenses"]: + print("No expenses for given id.") + return + c_list = state["expenses"][choice] + if not c_list: + print("No expenses to sum.") + return + prod = 1 + for c in c_list: + prod = (prod * int(c)) % n + m = pow(prod, d, n) + s = homo_rsa_discrete_log(m, base, n, max_exp) + print("Homomorphic sum result:") + print(f"combined ciphertext (mod n): {prod}") + if s is None: + print("decrypted sum: could not recover (out of range)") + else: + print(f"decrypted sum: {s}") + +def list_doctors(state): + if not state["doctors"]: + print("No doctors.") + return + for doc_id, d in state["doctors"].items(): + print(f"{doc_id}: {d['name']} dept='{d['department']}'") + +def doctor_list_my_data(state): + doc_id = input("Enter your doctor id: ").strip() + if doc_id not in state["doctors"]: + print("Unknown doctor.") + return + print("Reports:") + for r in state["reports"]: + if r["doctor_id"] == doc_id: + print(f"{r['report_id']} {r['filename']} {r['timestamp_utc']}") + print("Expenses (ciphertexts):") + for c in state["expenses"].get(doc_id, []): + print(c) + +def main(): + ensure_dirs() + state = load_state() + while True: + print("\nMain Menu") + print("1. Setup server keys") + print("2. Register doctor") + print("3. Doctor menu") + print("4. Auditor menu") + print("5. List doctors") + print("0. Exit") + ch = input("Choice: ").strip() + if ch == "1": + gen_server_keys(state) + elif ch == "2": + if "rsa_oaep" not in state["server"]: + print("Setup server keys first.") + else: + register_doctor(state) + elif ch == "3": + while True: + print("\nDoctor Menu") + print("1. Submit report (md file in inputdata/)") + print("2. Submit expense (homomorphic RSA)") + print("3. List my reports/expenses") + print("0. Back") + dch = input("Choice: ").strip() + if dch == "1": + if "rsa_oaep" not in state["server"]: + print("Setup server keys first.") + else: + doctor_submit_report(state) + elif dch == "2": + if "homo_rsa" not in state["server"]: + print("Setup server keys first.") + else: + doctor_submit_expense(state) + elif dch == "3": + doctor_list_my_data(state) + elif dch == "0": + break + else: + print("Invalid.") + elif ch == "4": + while True: + print("\nAuditor Menu") + print("1. List reports") + print("2. Verify a report (sig + timestamp)") + print("3. Dept keyword search (Paillier)") + print("4. Sum expenses (homomorphic RSA)") + print("0. Back") + ach = input("Choice: ").strip() + if ach == "1": + auditor_list_reports(state) + elif ach == "2": + auditor_verify_report(state) + elif ach == "3": + if "paillier" not in state["server"]: + print("Setup server keys first.") + else: + auditor_keyword_search(state) + elif ach == "4": + if "homo_rsa" not in state["server"]: + print("Setup server keys first.") + else: + auditor_sum_expenses(state) + elif ach == "0": + break + else: + print("Invalid.") + elif ch == "5": + list_doctors(state) + elif ch == "0": + print("Bye.") + break + else: + print("Invalid.") + +if __name__ == "__main__": + main() diff --git a/IS/Lab/Eval-Endsem/client.py b/IS/Lab/Eval-Endsem/client.py new file mode 100644 index 0000000..022bf6b --- /dev/null +++ b/IS/Lab/Eval-Endsem/client.py @@ -0,0 +1,47 @@ +import os +import json +import socket +import base64 +import hashlib +from datetime import datetime +from Crypto.PublicKey import RSA, ElGamal +from Crypto.Random import get_random_bytes +from Crypto.Util.number import inverse, GCD +from Crypto.Cipher import AES, PKCS1_OAEP +from phe import paillier + +SERVER_HOST = "127.0.0.1" +SERVER_PORT = 5000 +INPUT_DIR = "inputdata" +KEYS_DIR = "client_keys" + +server_info = {} # filled by get_public_info +cached_paillier_pub = None + +def ensure_dirs(): + os.makedirs(INPUT_DIR, exist_ok=True) + os.makedirs(KEYS_DIR, exist_ok=True) + +def b64e(b: bytes) -> str: + return base64.b64encode(b).decode() + +def b64d(s: str) -> bytes: + return base64.b64decode(s.encode()) + +def send_request(action, role, body): + req = {"action": action, "role": role, "body": body} + with socket.create_connection((SERVER_HOST, SERVER_PORT), timeout=5) as s: + s.sendall((json.dumps(req) + "\n").encode()) + data = s.recv(1024*1024) + resp = json.loads(data.decode()) + if resp.get("status") != "ok": + print("server error:", resp.get("error")) + return None + return resp.get("data") if "data" in resp else {} + +def get_public_info(): + global server_info, cached_paillier_pub + data = send_request("get result:") + if data is None: + +def rs diff --git a/IS/Lab/Eval-Endsem/inputdata/diag2.md b/IS/Lab/Eval-Endsem/inputdata/diag2.md new file mode 100644 index 0000000..c52e20f --- /dev/null +++ b/IS/Lab/Eval-Endsem/inputdata/diag2.md @@ -0,0 +1,5 @@ +hello +world +test +medical +doc \ No newline at end of file diff --git a/IS/Lab/Eval-Endsem/inputdata/diagnosis.md b/IS/Lab/Eval-Endsem/inputdata/diagnosis.md new file mode 100644 index 0000000..f10c38c --- /dev/null +++ b/IS/Lab/Eval-Endsem/inputdata/diagnosis.md @@ -0,0 +1,19 @@ +Patient - Aadit + +Patient No - 230953344 + +Blood Grp - A Positive + +Org - MIT Manipal + +Ailments +-------- +Allergic Rhinitis +Pancreatic Rest +Migraine + +Medicine +-------- +Pantoprazole +SOS inhaler +Omeprazole \ No newline at end of file diff --git a/IS/Lab/Eval-Endsem/q.md b/IS/Lab/Eval-Endsem/q.md new file mode 100644 index 0000000..e69de29 diff --git a/IS/Lab/Eval-Endsem/rough.md b/IS/Lab/Eval-Endsem/rough.md new file mode 100644 index 0000000..68b4826 --- /dev/null +++ b/IS/Lab/Eval-Endsem/rough.md @@ -0,0 +1,407 @@ +import os +import json +import threading +import socketserver +import base64 +import time +from datetime import datetime, timezone +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP, AES +from Crypto.Random import get_random_bytes +from Crypto.Util.number import GCD +from phe import paillier + +DATA_DIR = server_data +DOCTORS_FILE = os.path.join(DATA_DIR, doctors.json) +EXPENSES_FILE = os.path.join(DATA_DIR, expenses.json) +REPORTS_FILE = os.path.join(DATA_DIR, reports.json) +CONF_FILE = os.path.join(DATA_DIR, config.json) +RSA_PRIV_FILE = os.path.join(DATA_DIR, server_rsa_priv.pem) +RSA_PUB_FILE = os.path.join(DATA_DIR, server_rsa_pub.pem) +PORT = 5000 + +lock = threading.Lock() + +def ensure_dirs(): + os.makedirs(DATA_DIR, exist_ok=True) + +def read_json(path, default): + if not os.path.exists(path): + return default + with open(path, r) as f: + return json.load(f) + +def write_json(path, obj): + tmp = path + .tmp + with open(tmp, w) as f: + json.dump(obj, f, indent=2) + os.replace(tmp, path) + +def load_or_create_rsa(): + if not os.path.exists(RSA_PRIV_FILE): + key = RSA.generate(2048) + with open(RSA_PRIV_FILE, wb) as f: + f.write(key.export_key()) + with open(RSA_PUB_FILE, wb) as f: + f.write(key.public_key().export_key()) + with open(RSA_PRIV_FILE, rb) as f: + priv = RSA.import_key(f.read()) + with open(RSA_PUB_FILE, rb) as f: + pub = RSA.import_key(f.read()) + return priv, pub + +def load_or_create_paillier(): + conf = read_json(CONF_FILE, {}) + if paillier not in conf: + pubkey, privkey = paillier.generate_paillier_keypair() + conf[paillier] = { + n: str(pubkey.n), + p: str(privkey.p), + q: str(privkey.q), + } + write_json(CONF_FILE, conf) + conf = read_json(CONF_FILE, {}) + n = int(conf[paillier][n]) + p = int(conf[paillier][p]) + q = int(conf[paillier][q]) + pubkey = paillier.PaillierPublicKey(n) + privkey = paillier.PaillierPrivateKey(pubkey, p, q) + return pubkey, privkey + +def load_or_create_config_rsa_homomorphic_base(rsa_pub): + conf = read_json(CONF_FILE, {}) + n = rsa_pub.n + if rsa_homomorphic not in conf: + # pick base g coprime to n + import random + while True: + g = random.randrange(2, n - 1) + if GCD(g, n) == 1: + break + conf[rsa_homomorphic] = { + g: str(g) + } + write_json(CONF_FILE, conf) + conf = read_json(CONF_FILE, {}) + g = int(conf[rsa_homomorphic][g]) + return g + +def b64e(b: bytes) -> str: + return base64.b64encode(b).decode() + +def b64d(s: str) -> bytes: + return base64.b64decode(s.encode()) + +def init_storage(): + ensure_dirs() + priv, pub = load_or_create_rsa() + _ = load_or_create_paillier() + if not os.path.exists(DOCTORS_FILE): + write_json(DOCTORS_FILE, {}) + if not os.path.exists(EXPENSES_FILE): + write_json(EXPENSES_FILE, []) + if not os.path.exists(REPORTS_FILE): + write_json(REPORTS_FILE, []) + return priv, pub + +RSA_PRIV, RSA_PUB = init_storage() +PAI_PUB, PAI_PRIV = load_or_create_paillier() +RSA_HOMO_G = load_or_create_config_rsa_homomorphic_base(RSA_PUB) + +def get_public_info(): + return { + rsa_pub_pem_b64: b64e(RSA_PUB.export_key()), + rsa_n: str(RSA_PUB.n), + rsa_e: str(RSA_PUB.e), + paillier_n: str(PAI_PUB.n), + rsa_homo_g: str(RSA_HOMO_G), + } + +def handle_register_doctor(body): + # body: {doctor_id, department_plain, dept_enc: {ciphertext, exponent}, elgamal_pub: {p,g,y}} + doc_id = body.get(doctor_id,).strip() + dept_plain = body.get(department_plain,).strip() + dept_enc = body.get(dept_enc) + elgamal_pub = body.get(elgamal_pub) + if not doc_id or not doc_id.isalnum(): + return {status:error,error:invalid doctor_id} + if not dept_plain: + return {status:error,error:invalid department} + if not dept_enc or ciphertext not in dept_enc or exponent not in dept_enc: + return {status:error,error:invalid dept_enc} + if not elgamal_pub or not all(k in elgamal_pub for k in [p,g,y]): + return {status:error,error:missing elgamal_pub} + + with lock: + doctors = read_json(DOCTORS_FILE, {}) + doctors[doc_id] = { + department_plain: dept_plain, + dept_enc: { + ciphertext: str(int(dept_enc[ciphertext])), + exponent: int(dept_enc[exponent]) + }, + elgamal_pub: { + p: str(int(elgamal_pub[p])), + g: str(int(elgamal_pub[g])), + y: str(int(elgamal_pub[y])) + } + } + write_json(DOCTORS_FILE, doctors) + print(f[server] registered doctor {doc_id} dept='{dept_plain}' (stored encrypted and plaintext)) + return {status:ok} + +def handle_upload_report(body): + # body: {doctor_id, filename, timestamp, md5_hex, sig: {r,s}, aes: {key_rsa_oaep_b64, nonce_b64, tag_b64, ct_b64}} + doc_id = body.get(doctor_id,).strip() + filename = os.path.basename(body.get(filename,).strip()) + timestamp = body.get(timestamp,).strip() + md5_hex = body.get(md5_hex,).strip() + sig = body.get(sig) + aes = body.get(aes) + if not doc_id or not filename or not timestamp or not md5_hex or not sig or not aes: + return {status:error,error:missing fields} + + with lock: + doctors = read_json(DOCTORS_FILE, {}) + if doc_id not in doctors: + return {status:error,error:unknown doctor_id} + + # decrypt AES key + try: + rsa_cipher = PKCS1_OAEP.new(RSA_PRIV) + aes_key = rsa_cipher.decrypt(b64d(aes[key_rsa_oaep_b64])) + nonce = b64d(aes[nonce_b64]) + tag = b64d(aes[tag_b64]) + ct = b64d(aes[ct_b64]) + aes_cipher = AES.new(aes_key, AES.MODE_EAX, nonce=nonce) + report_bytes = aes_cipher.decrypt_and_verify(ct, tag) + except Exception as e: + return {status:error,error:faes/rsa decrypt failed: {e}} + + # verify MD5 + import hashlib + md5_check = hashlib.md5(report_bytes).hexdigest() + if md5_check != md5_hex: + print([server] md5 mismatch) + # store file + outdir = os.path.join(DATA_DIR, reports) + os.makedirs(outdir, exist_ok=True) + savepath = os.path.join(outdir, f{doc_id}_{int(time.time())}_{filename}) + with open(savepath, wb) as f: + f.write(report_bytes) + + # store record + rec = { + doctor_id: doc_id, + filename: filename, + saved_path: savepath, + timestamp: timestamp, + md5_hex: md5_hex, + sig: {r: str(int(sig[r])), s: str(int(sig[s]))} + } + with lock: + records = read_json(REPORTS_FILE, []) + records.append(rec) + write_json(REPORTS_FILE, records) + print(f[server] report uploaded by {doc_id}, stored {savepath}) + return {status:ok} + +def handle_submit_expense(body): + # body: {doctor_id, amount_ciphertext} + doc_id = body.get(doctor_id,).strip() + c = body.get(amount_ciphertext) + if not doc_id or not doc_id.isalnum(): + return {status:error,error:invalid doctor_id} + try: + c_int = int(c) + except: + return {status:error,error:invalid ciphertext} + with lock: + doctors = read_json(DOCTORS_FILE, {}) + if doc_id not in doctors: + return {status:error,error:unknown doctor_id} + + with lock: + expenses = read_json(EXPENSES_FILE, []) + expenses.append({doctor_id: doc_id, ciphertext: str(c_int)}) + write_json(EXPENSES_FILE, expenses) + print(f[server] expense ciphertext stored for {doc_id}) + return {status:ok} + +class RequestHandler(socketserver.StreamRequestHandler): + def handle(self): + try: + data = self.rfile.readline() + if not data: + return + req = json.loads(data.decode()) + action = req.get(action) + role = req.get(role, ) + body = req.get(body, {}) + if action == get_public_info: + resp = {status:ok,data: get_public_info()} + elif action == register_doctor: + if role != doctor: + resp = {status:error,error:unauthorized} + else: + resp = handle_register_doctor(body) + elif action == upload_report: + if role != doctor: + resp = {status:error,error:unauthorized} + else: + resp = handle_upload_report(body) + elif action == submit_expense: + if role != doctor: + resp = {status:error,error:unauthorized} + else: + resp = handle_submit_expense(body) + else: + resp = {status:error,error:unknown action} + except Exception as e: + resp = {status:error,error:str(e)} + self.wfile.write((json.dumps(resp)+\n).encode()) + +def start_server(): + server = socketserver.ThreadingTCPServer((127.0.0.1, PORT), RequestHandler) + t = threading.Thread(target=server.serve_forever, daemon=True) + t.start() + print(f[server] listening on 127.0.0.1:{PORT}) + return server + +# Auditor utilities + +def load_doctors(): + return read_json(DOCTORS_FILE, {}) + +def load_expenses(): + return read_json(EXPENSES_FILE, []) + +def load_reports(): + return read_json(REPORTS_FILE, []) + +def audit_list_doctors(): + docs = load_doctors() + print(Doctors:) + for did, info in docs.items(): + enc = info[dept_enc] + print(f- {did} dept_plain='{info['department_plain']}' enc_ciphertext={enc['ciphertext']} exponent={enc['exponent']}) + +def audit_keyword_search(): + docs = load_doctors() + if not docs: + print(no doctors) + return + q = input(Enter department keyword to search: ).strip() + if not q: + print(empty) + return + # hash to int + import hashlib + h = int.from_bytes(hashlib.sha256(q.encode()).digest(), big) + pub = PAI_PUB + priv = PAI_PRIV + enc_q = pub.encrypt(h) + print(Matching doctors (using Paillier equality on hashed dept):) + for did, info in docs.items(): + enc = info[dept_enc] + c = int(enc[ciphertext]) + exp = int(enc[exponent]) + enc_doc = paillier.EncryptedNumber(pub, c, exp) + diff = enc_doc - enc_q + dec = priv.decrypt(diff) + match = (dec == 0) + print(f {did}: dept_plain='{info['department_plain']}' enc_ciphertext={c} match={match}) + +def rsa_homo_decrypt_sum(c_prod_int): + n = RSA_PRIV.n + d = RSA_PRIV.d + g = RSA_HOMO_G + # decrypt to get g^sum mod n + m = pow(int(c_prod_int), d, n) + # brute force discrete log for small sums + max_iter = 500000 + acc = 1 + for k in range(0, max_iter+1): + if acc == m: + return k + acc = (acc * g) % n + return None + +def audit_sum_expenses(): + exps = load_expenses() + if not exps: + print(no expenses) + return + # sum all + n = RSA_PUB.n + c_prod = 1 + for e in exps: + c_prod = (c_prod * int(e[ciphertext])) % n + print(fProduct ciphertext (represents sum under RSA-in-exponent): {c_prod}) + s = rsa_homo_decrypt_sum(c_prod) + if s is None: + print(sum decryption failed (exceeded search bound)) + else: + print(fDecrypted sum of expenses = {s}) + # by doctor + docs = load_doctors() + if docs: + print(Per-doctor sums:) + for did in docs.keys(): + c_prod_d = 1 + count = 0 + for e in exps: + if e[doctor_id] == did: + c_prod_d = (c_prod_d * int(e[ciphertext])) % n + count += 1 + if count == 0: + continue + s_d = rsa_homo_decrypt_sum(c_prod_d) + print(f {did}: entries={count} product_ct={c_prod_d} sum={s_d}) + +def elgamal_verify(p, g, y, H_int, r, s): + # verify: g^H ≡ y^r * r^s (mod p) + return pow(g, H_int, p) == (pow(y, r, p) * pow(r, s, p)) % p + +def audit_verify_reports(): + records = load_reports() + if not records: + print(no reports) + return + doctors = load_doctors() + for rec in records: + did = rec[doctor_id] + docinfo = doctors.get(did) + ok_sig = False + ok_ts = False + if docinfo: + p = int(docinfo[elgamal_pub][p]) + g = int(docinfo[elgamal_pub][g]) + y = int(docinfo[elgamal_pub][y]) + r = int(rec[sig][r]) + s = int(rec[sig][s]) + try: + with open(rec[saved_path], rb) as f: + report_bytes = f.read() + import hashlib + H = int.from_bytes(hashlib.md5(report_bytes + rec[timestamp].encode()).digest(), big) % (p - 1) + ok_sig = elgamal_verify(p, g, y, H, r, s) + except Exception as e: + ok_sig = False + # timestamp check + try: + ts = datetime.fromisoformat(rec[timestamp]) + except: + try: + ts = datetime.strptime(rec[timestamp], %Y-%m-%dT%H:%M:%S.%f) + except: + ts = None + if ts: + now = datetime.utcnow().replace(tzinfo=None) + delta = (now - ts).total_seconds() + # simple rule: not in the future by more than 5 min + ok_ts = (delta >= -300) + print(f- report by {did} file={os.path.basename(rec['saved_path'])} sig_ok={ok_sig} ts_ok={ok_ts} ts={rec['timestamp']} md5={rec['md5_hex']}) + +def auditor_menu(): while True: print(\n[Auditor Menu]) print(1) List doctors (show encrypted and plaintext dept)) print(2) Keyword search doctors by dept (Paillier)) print(3) Sum expenses (RSA-in-exponent demo)) print(4) Verify reports and timestamps) print(5) Show server public info) print(0) Exit) ch = input(Select: ).strip() if ch == 1: audit_list_doctors() elif ch == 2: audit_keyword_search() elif ch == 3: audit_sum_expenses() elif ch == 4: audit_verify_reports() elif ch == 5: info = get_public_info() print(json.dumps(info, indent=2)) elif ch == 0: print(bye) break else: print(invalid) if __name__ == __main__: start_server() auditor_menu() + diff --git a/IS/Lab/Eval-Endsem/server.py b/IS/Lab/Eval-Endsem/server.py new file mode 100644 index 0000000..c93a87a --- /dev/null +++ b/IS/Lab/Eval-Endsem/server.py @@ -0,0 +1,436 @@ +import os +import json +import threading +import socketserver +import base64 +import time +from datetime import datetime, timezone +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP, AES +from Crypto.Random import get_random_bytes +from Crypto.Util.number import GCD +from phe import paillier + +DATA_DIR = "server_data" +DOCTORS_FILE = os.path.join(DATA_DIR, "doctors.json") +EXPENSES_FILE = os.path.join(DATA_DIR, "expenses.json") +REPORTS_FILE = os.path.join(DATA_DIR, "reports.json") +CONF_FILE = os.path.join(DATA_DIR, "config.json") +RSA_PRIV_FILE = os.path.join(DATA_DIR, "server_rsa_priv.pem") +RSA_PUB_FILE = os.path.join(DATA_DIR, "server_rsa_pub.pem") +PORT = 5000 + +lock = threading.Lock() + +def ensure_dirs(): + os.makedirs(DATA_DIR, exist_ok=True) + +def read_json(path, default): + if not os.path.exists(path): + return default + with open(path, "r") as f: + return json.load(f) + +def write_json(path, obj): + tmp = path + ".tmp" + with open(tmp, "w") as f: + json.dump(obj, f, indent=2) + os.replace(tmp, path) + +def load_or_create_rsa(): + if not os.path.exists(RSA_PRIV_FILE): + key = RSA.generate(2048) + with open(RSA_PRIV_FILE, "wb") as f: + f.write(key.export_key()) + with open(RSA_PUB_FILE, "wb") as f: + f.write(key.public_key().export_key()) + with open(RSA_PRIV_FILE, "rb") as f: + priv = RSA.import_key(f.read()) + with open(RSA_PUB_FILE, "rb") as f: + pub = RSA.import_key(f.read()) + return priv, pub + +def load_or_create_paillier(): + conf = read_json(CONF_FILE, {}) + if "paillier" not in conf: + pubkey, privkey = paillier.generate_paillier_keypair() + conf["paillier"] = { + "n": str(pubkey.n), + "p": str(privkey.p), + "q": str(privkey.q), + } + write_json(CONF_FILE, conf) + conf = read_json(CONF_FILE, {}) + n = int(conf["paillier"]["n"]) + p = int(conf["paillier"]["p"]) + q = int(conf["paillier"]["q"]) + pubkey = paillier.PaillierPublicKey(n) + privkey = paillier.PaillierPrivateKey(pubkey, p, q) + return pubkey, privkey + +def load_or_create_config_rsa_homomorphic_base(rsa_pub): + conf = read_json(CONF_FILE, {}) + n = rsa_pub.n + if "rsa_homomorphic" not in conf: + # pick base g coprime to n + import random + while True: + g = random.randrange(2, n - 1) + if GCD(g, n) == 1: + break + conf["rsa_homomorphic"] = { + "g": str(g) + } + write_json(CONF_FILE, conf) + conf = read_json(CONF_FILE, {}) + g = int(conf["rsa_homomorphic"]["g"]) + return g + +def b64e(b: bytes) -> str: + return base64.b64encode(b).decode() + +def b64d(s: str) -> bytes: + return base64.b64decode(s.encode()) + +def init_storage(): + ensure_dirs() + priv, pub = load_or_create_rsa() + _ = load_or_create_paillier() + if not os.path.exists(DOCTORS_FILE): + write_json(DOCTORS_FILE, {}) + if not os.path.exists(EXPENSES_FILE): + write_json(EXPENSES_FILE, []) + if not os.path.exists(REPORTS_FILE): + write_json(REPORTS_FILE, []) + return priv, pub + +RSA_PRIV, RSA_PUB = init_storage() +PAI_PUB, PAI_PRIV = load_or_create_paillier() +RSA_HOMO_G = load_or_create_config_rsa_homomorphic_base(RSA_PUB) + +def get_public_info(): + return { + "rsa_pub_pem_b64": b64e(RSA_PUB.export_key()), + "rsa_n": str(RSA_PUB.n), + "rsa_e": str(RSA_PUB.e), + "paillier_n": str(PAI_PUB.n), + "rsa_homo_g": str(RSA_HOMO_G), + } + +def handle_register_doctor(body): + # body: {doctor_id, department_plain, dept_enc: {ciphertext, exponent}, elgamal_pub: {p,g,y}} + doc_id = body.get("doctor_id","").strip() + dept_plain = body.get("department_plain","").strip() + dept_enc = body.get("dept_enc") + elgamal_pub = body.get("elgamal_pub") + if not doc_id or not doc_id.isalnum(): + return {"status":"error","error":"invalid doctor_id"} + if not dept_plain: + return {"status":"error","error":"invalid department"} + if not dept_enc or "ciphertext" not in dept_enc or "exponent" not in dept_enc: + return {"status":"error","error":"invalid dept_enc"} + if not elgamal_pub or not all(k in elgamal_pub for k in ["p","g","y"]): + return {"status":"error","error":"missing elgamal_pub"} + + with lock: + doctors = read_json(DOCTORS_FILE, {}) + doctors[doc_id] = { + "department_plain": dept_plain, + "dept_enc": { + "ciphertext": str(int(dept_enc["ciphertext"])), + "exponent": int(dept_enc["exponent"]) + }, + "elgamal_pub": { + "p": str(int(elgamal_pub["p"])), + "g": str(int(elgamal_pub["g"])), + "y": str(int(elgamal_pub["y"])) + } + } + write_json(DOCTORS_FILE, doctors) + print(f"[server] registered doctor {doc_id} dept='{dept_plain}' (stored encrypted and plaintext)") + return {"status":"ok"} + +def handle_upload_report(body): + # body: {doctor_id, filename, timestamp, md5_hex, sig: {r,s}, aes: {key_rsa_oaep_b64, nonce_b64, tag_b64, ct_b64}} + doc_id = body.get("doctor_id","").strip() + filename = os.path.basename(body.get("filename","").strip()) + timestamp = body.get("timestamp","").strip() + md5_hex = body.get("md5_hex","").strip() + sig = body.get("sig") + aes = body.get("aes") + if not doc_id or not filename or not timestamp or not md5_hex or not sig or not aes: + return {"status":"error","error":"missing fields"} + + with lock: + doctors = read_json(DOCTORS_FILE, {}) + if doc_id not in doctors: + return {"status":"error","error":"unknown doctor_id"} + + # decrypt AES key + try: + rsa_cipher = PKCS1_OAEP.new(RSA_PRIV) + aes_key = rsa_cipher.decrypt(b64d(aes["key_rsa_oaep_b64"])) + nonce = b64d(aes["nonce_b64"]) + tag = b64d(aes["tag_b64"]) + ct = b64d(aes["ct_b64"]) + aes_cipher = AES.new(aes_key, AES.MODE_EAX, nonce=nonce) + report_bytes = aes_cipher.decrypt_and_verify(ct, tag) + except Exception as e: + return {"status":"error","error":f"aes/rsa decrypt failed: {e}"} + + # verify MD5 + import hashlib + md5_check = hashlib.md5(report_bytes).hexdigest() + if md5_check != md5_hex: + print("[server] md5 mismatch") + # store file + outdir = os.path.join(DATA_DIR, "reports") + os.makedirs(outdir, exist_ok=True) + savepath = os.path.join(outdir, f"{doc_id}_{int(time.time())}_{filename}") + with open(savepath, "wb") as f: + f.write(report_bytes) + + # store record + rec = { + "doctor_id": doc_id, + "filename": filename, + "saved_path": savepath, + "timestamp": timestamp, + "md5_hex": md5_hex, + "sig": {"r": str(int(sig["r"])), "s": str(int(sig["s"]))} + } + with lock: + records = read_json(REPORTS_FILE, []) + records.append(rec) + write_json(REPORTS_FILE, records) + print(f"[server] report uploaded by {doc_id}, stored {savepath}") + return {"status":"ok"} + +def handle_submit_expense(body): + # body: {doctor_id, amount_ciphertext} + doc_id = body.get("doctor_id","").strip() + c = body.get("amount_ciphertext") + if not doc_id or not doc_id.isalnum(): + return {"status":"error","error":"invalid doctor_id"} + try: + c_int = int(c) + except: + return {"status":"error","error":"invalid ciphertext"} + with lock: + doctors = read_json(DOCTORS_FILE, {}) + if doc_id not in doctors: + return {"status":"error","error":"unknown doctor_id"} + + with lock: + expenses = read_json(EXPENSES_FILE, []) + expenses.append({"doctor_id": doc_id, "ciphertext": str(c_int)}) + write_json(EXPENSES_FILE, expenses) + print(f"[server] expense ciphertext stored for {doc_id}") + return {"status":"ok"} + +class RequestHandler(socketserver.StreamRequestHandler): + def handle(self): + try: + data = self.rfile.readline() + if not data: + return + req = json.loads(data.decode()) + action = req.get("action") + role = req.get("role", "") + body = req.get("body", {}) + if action == "get_public_info": + resp = {"status":"ok","data": get_public_info()} + elif action == "register_doctor": + if role != "doctor": + resp = {"status":"error","error":"unauthorized"} + else: + resp = handle_register_doctor(body) + elif action == "upload_report": + if role != "doctor": + resp = {"status":"error","error":"unauthorized"} + else: + resp = handle_upload_report(body) + elif action == "submit_expense": + if role != "doctor": + resp = {"status":"error","error":"unauthorized"} + else: + resp = handle_submit_expense(body) + else: + resp = {"status":"error","error":"unknown action"} + except Exception as e: + resp = {"status":"error","error":str(e)} + self.wfile.write((json.dumps(resp)+"\n").encode()) + +def start_server(): + server = socketserver.ThreadingTCPServer(("127.0.0.1", PORT), RequestHandler) + t = threading.Thread(target=server.serve_forever, daemon=True) + t.start() + print(f"[server] listening on 127.0.0.1:{PORT}") + return server + +# Auditor utilities + +def load_doctors(): + return read_json(DOCTORS_FILE, {}) + +def load_expenses(): + return read_json(EXPENSES_FILE, []) + +def load_reports(): + return read_json(REPORTS_FILE, []) + +def audit_list_doctors(): + docs = load_doctors() + print("Doctors:") + for did, info in docs.items(): + enc = info["dept_enc"] + print(f"- {did} dept_plain='{info['department_plain']}' enc_ciphertext={enc['ciphertext']} exponent={enc['exponent']}") + +def audit_keyword_search(): + docs = load_doctors() + if not docs: + print("no doctors") + return + q = input("Enter department keyword to search: ").strip() + if not q: + print("empty") + return + # hash to int + import hashlib + h = int.from_bytes(hashlib.sha256(q.encode()).digest(), "big") + pub = PAI_PUB + priv = PAI_PRIV + enc_q = pub.encrypt(h) + print("Matching doctors (using Paillier equality on hashed dept):") + for did, info in docs.items(): + enc = info["dept_enc"] + c = int(enc["ciphertext"]) + exp = int(enc["exponent"]) + enc_doc = paillier.EncryptedNumber(pub, c, exp) + diff = enc_doc - enc_q + dec = priv.decrypt(diff) + match = (dec == 0) + print(f" {did}: dept_plain='{info['department_plain']}' enc_ciphertext={c} match={match}") + +def rsa_homo_decrypt_sum(c_prod_int): + n = RSA_PRIV.n + d = RSA_PRIV.d + g = RSA_HOMO_G + # decrypt to get g^sum mod n + m = pow(int(c_prod_int), d, n) + # brute force discrete log for small sums + max_iter = 500000 + acc = 1 + for k in range(0, max_iter+1): + if acc == m: + return k + acc = (acc * g) % n + return None + +def audit_sum_expenses(): + exps = load_expenses() + if not exps: + print("no expenses") + return + # sum all + n = RSA_PUB.n + c_prod = 1 + for e in exps: + c_prod = (c_prod * int(e["ciphertext"])) % n + print(f"Product ciphertext (represents sum under RSA-in-exponent): {c_prod}") + s = rsa_homo_decrypt_sum(c_prod) + if s is None: + print("sum decryption failed (exceeded search bound)") + else: + print(f"Decrypted sum of expenses = {s}") + # by doctor + docs = load_doctors() + if docs: + print("Per-doctor sums:") + for did in docs.keys(): + c_prod_d = 1 + count = 0 + for e in exps: + if e["doctor_id"] == did: + c_prod_d = (c_prod_d * int(e["ciphertext"])) % n + count += 1 + if count == 0: + continue + s_d = rsa_homo_decrypt_sum(c_prod_d) + print(f" {did}: entries={count} product_ct={c_prod_d} sum={s_d}") + +def elgamal_verify(p, g, y, H_int, r, s): + # verify: g^H ≡ y^r * r^s (mod p) + return pow(g, H_int, p) == (pow(y, r, p) * pow(r, s, p)) % p + +def audit_verify_reports(): + records = load_reports() + if not records: + print("no reports") + return + doctors = load_doctors() + for rec in records: + did = rec["doctor_id"] + docinfo = doctors.get(did) + ok_sig = False + ok_ts = False + if docinfo: + p = int(docinfo["elgamal_pub"]["p"]) + g = int(docinfo["elgamal_pub"]["g"]) + y = int(docinfo["elgamal_pub"]["y"]) + r = int(rec["sig"]["r"]) + s = int(rec["sig"]["s"]) + try: + with open(rec["saved_path"], "rb") as f: + report_bytes = f.read() + import hashlib + H = int.from_bytes(hashlib.md5(report_bytes + rec["timestamp"].encode()).digest(), "big") % (p - 1) + ok_sig = elgamal_verify(p, g, y, H, r, s) + except Exception as e: + ok_sig = False + # timestamp check + try: + ts = datetime.fromisoformat(rec["timestamp"]) + except: + try: + ts = datetime.strptime(rec["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") + except: + ts = None + if ts: + now = datetime.utcnow().replace(tzinfo=None) + delta = (now - ts).total_seconds() + # simple rule: not in the future by more than 5 min + ok_ts = (delta >= -300) + print(f"- report by {did} file={os.path.basename(rec['saved_path'])} sig_ok={ok_sig} ts_ok={ok_ts} ts={rec['timestamp']} md5={rec['md5_hex']}") + +def auditor_menu(): + while True: + print("\n[Auditor Menu]") + print("1) List doctors (show encrypted and plaintext dept)") + print("2) Keyword search doctors by dept (Paillier)") + print("3) Sum expenses (RSA-in-exponent demo)") + print("4) Verify reports and timestamps") + print("5) Show server public info") + print("0) Exit") + ch = input("Select: ").strip() + if ch == "1": + audit_list_doctors() + elif ch == "2": + audit_keyword_search() + elif ch == "3": + audit_sum_expenses() + elif ch == "4": + audit_verify_reports() + elif ch == "5": + info = get_public_info() + print(json.dumps(info, indent=2)) + elif ch == "0": + print("bye") + break + else: + print("invalid") + +if __name__ == "__main__": + start_server() + auditor_menu() + diff --git a/IS/Lab/Lab6/socket_rsa/client.py b/IS/Lab/Lab6/socket_rsa/client.py index 08460b3..542b463 100644 --- a/IS/Lab/Lab6/socket_rsa/client.py +++ b/IS/Lab/Lab6/socket_rsa/client.py @@ -46,4 +46,5 @@ def main() -> None: if __name__ == "__main__": main() +curl diff --git a/IS/Lab/Lab8/PKSE/encrypted_index.pkl b/IS/Lab/Lab8/PKSE/encrypted_index.pkl index 5082e65..de1e38e 100644 Binary files a/IS/Lab/Lab8/PKSE/encrypted_index.pkl and b/IS/Lab/Lab8/PKSE/encrypted_index.pkl differ