Secure Banking System
A from-scratch secure banking client-server system - a hand-rolled authenticated key exchange establishes per-session keys, then every deposit, withdrawal, and balance check is individually encrypted and authenticated over the wire.
Why it exists
A simulated bank server and ATM client where security is the actual subject, not an afterthought bolted onto a CRUD app - built to understand what a real authenticated protocol has to do at the byte level, without leaning on an existing TLS library to do it invisibly.
The handshake
Login doesn't just check a password - it bootstraps a whole new set of keys for everything that follows:
{ username, password, nonce_client } - encrypted + HMAC’d with the pre-shared key
{ nonce_client, nonce_server, master_secret } - same pre-shared key
both sides independently derive MasterSecret = SHA256(shared_key ‖ nonce_client ‖ nonce_server), then split it into a fresh enc_key and mac_key for this session
{ action: "deposit", amount } - encrypted + HMAC’d with the new session keys
{ status, balance } - same session keys, every transaction re-sealed independently
Every transaction, sealed twice
Nothing rides on one long encrypted pipe - login, a deposit, a balance check, each one is its own self-contained envelope, built the same way every time:
Exactly what goes out over the socket. The receiving side splits the last 32 bytes off first and verifies them against everything before it - decryption never even starts if that check fails.
A log even the server operator can't casually read
Every deposit, withdrawal, and balance inquiry goes through the same transform before it ever touches disk:
Session key
Decrypts your own transactions. Gone the moment you log out.
Log key
Decrypts the audit trail. Never sent anywhere - it just stays on the server.
What's honestly still a demo
A few simplifications, left in on purpose rather than hidden:
Plaintext passwords
users.json stores them as-is - the source even says so in its own comment.
In-memory balances
Every balance lives in a dict on the server. Restart it, and the bank forgets everyone.
The master secret rides along
It's sent back to the client instead of purely derived in parallel - safe only because that message is itself already sealed under the pre-shared key.
Solo work in Python with PyCryptodome for the actual cryptography, raw sockets for transport, and Tkinter GUIs wrapping both the client and the server on top of the same core logic used by their terminal versions.