labendsem
This commit is contained in:
parent
040219580e
commit
9d70a80337
9 changed files with 1349 additions and 0 deletions
434
IS/Lab/Eval-Endsem/ans.py
Normal file
434
IS/Lab/Eval-Endsem/ans.py
Normal file
|
|
@ -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()
|
||||
47
IS/Lab/Eval-Endsem/client.py
Normal file
47
IS/Lab/Eval-Endsem/client.py
Normal file
|
|
@ -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
|
||||
5
IS/Lab/Eval-Endsem/inputdata/diag2.md
Normal file
5
IS/Lab/Eval-Endsem/inputdata/diag2.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
hello
|
||||
world
|
||||
test
|
||||
medical
|
||||
doc
|
||||
19
IS/Lab/Eval-Endsem/inputdata/diagnosis.md
Normal file
19
IS/Lab/Eval-Endsem/inputdata/diagnosis.md
Normal file
|
|
@ -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
|
||||
0
IS/Lab/Eval-Endsem/q.md
Normal file
0
IS/Lab/Eval-Endsem/q.md
Normal file
407
IS/Lab/Eval-Endsem/rough.md
Normal file
407
IS/Lab/Eval-Endsem/rough.md
Normal file
|
|
@ -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()
|
||||
|
||||
436
IS/Lab/Eval-Endsem/server.py
Normal file
436
IS/Lab/Eval-Endsem/server.py
Normal file
|
|
@ -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()
|
||||
|
||||
|
|
@ -46,4 +46,5 @@ def main() -> None:
|
|||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
curl
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue