A hands-on applied-cryptography project in two parts: an encrypted client-server chat (each client-server link has its own session key) built on modern primitives (ECDH key exchange, HKDF, AES-256-GCM, RSA-PSS signatures), and interactive demos of classic attacks on badly used cryptography (ECB pattern leakage, CBC padding oracle, birthday collisions). It shows both how to build a secure channel correctly and why common mistakes break.
Two parts:
- Part A — a working encrypted chat (client-server) with ECDH key exchange, AES-256-GCM encryption, RSA signatures
- Part B — three demos that show why bad crypto implementations get broken (ECB penguin, padding oracle, birthday attack)
secure_chat/
crypto_utils.py — all the crypto functions (ECDH, AES-GCM, RSA, HKDF)
server.py — multi-client chat server
client.py — terminal chat client
attack_demos/
streamlit_app.py — web UI for the demos
demos/
ecb_penguin.py
padding_oracle.py
birthday_attack.py
logs/ — handshake logs get dumped here
python -m venv venv
venv\Scripts\activate # windows
# source venv/bin/activate # linux/mac
pip install -r requirements.txtNeeds Python 3.9+.
When a client connects, this is what happens before any messages get sent:
CLIENT SERVER
| |
| --- TCP connect ------------------> |
| <-- server ECDH pub key (PEM) ----- | server makes ephemeral ECDH keypair
| --- client ECDH pub key (PEM) ----> | client makes its own
| |
| both sides do ECDH -> shared secret |
| run HKDF-SHA256 -> 256-bit AES key |
| |
| <-- server RSA pub key (PEM) ------ | for message signing
| --- client RSA pub key (PEM) -----> |
| --- nickname ---------------------> |
| |
| === encrypted channel ready ======== |
| <-> AES-256-GCM + RSA-PSS sigs <-> |
- ECDH (SECP384R1) for key exchange — gives forward secrecy, so even if keys leak later the old messages are safe. SECP384R1 is ~192-bit security level
- HKDF-SHA256 for key derivation — raw ECDH output isn't uniformly random, HKDF fixes that
- AES-256-GCM for encryption — authenticated encryption, handles confidentiality + integrity together. If someone modifies the ciphertext it just fails to decrypt
- RSA-2048 with PSS padding for signatures — PSS is the modern padding scheme, better security guarantees than the older PKCS#1 v1.5
Start the server:
python -m secure_chat.serverThen connect clients (in separate terminals):
python -m secure_chat.client --nick Alice
python -m secure_chat.client --nick BobType messages and they show up on the other side. Type quit to disconnect. Logs go to logs/server.log and logs/client.log.
Messages are sent as length-prefixed JSON over TCP:
[4 bytes length][JSON payload]
Encrypted message looks like:
{
"type": "message",
"nonce": "hex 96-bit GCM nonce",
"ciphertext": "hex AES-GCM ciphertext + tag",
"signature": "hex RSA-PSS signature over nonce||ciphertext"
}streamlit run attack_demos/streamlit_app.pyOpens at localhost:8501. Has three tabs for the three demos.
Or run them individually:
python -m attack_demos.demos.ecb_penguin
python -m attack_demos.demos.padding_oracle
python -m attack_demos.demos.birthday_attackEncrypts an image in ECB vs CBC mode. ECB encrypts blocks independently so patterns in the image are still visible after encryption. CBC chains blocks together so the output looks like noise. This is why you never use ECB for anything with structure.
Sets up a vulnerable CBC server that leaks whether padding is valid. The attack script exploits this to recover plaintext byte by byte without the key. Also shows the secure version (AES-GCM) where this attack is impossible since there's no padding to begin with.
Based on the Vaudenay attack (2002). Same class of bug behind POODLE and Lucky13 in TLS.
Shows that finding hash collisions is way easier than brute force. For an n-bit hash you only need ~2^(n/2) attempts (birthday paradox). The demo truncates SHA-256 to small sizes and finds collisions in real time, then compares across different hash sizes to show the scaling.
- Python 3.9+
- cryptography (ECDH SECP384R1, HKDF-SHA256, AES-256-GCM, RSA-2048 PSS, AES-ECB/CBC for the demos)
- Python sockets + threading for the multi-client chat server
- Streamlit for the attack-demo dashboard
- Pillow for the ECB image demo
This is a learning project, not production software. Things I'd need to add for real use:
- Not end-to-end: the server decrypts and re-encrypts each message, so it can read everything that is relayed
- No certificate authority — the chat just trusts whatever RSA key it gets during handshake (vulnerable to MITM)
- No key rotation — same session key for the whole connection
- No cross-session replay protection
- Forward secrecy depends on ephemeral keys actually being deleted from memory
These are documented on purpose — knowing what your system doesn't protect against matters just as much.
Future work: certificate-based or pinned-key authentication of the RSA keys (to stop MITM), periodic key rotation, message counters for replay protection, and automated tests for the protocol.
The attack demos run only against the toy servers included in this repo, to show why these implementation mistakes are dangerous. Don't use these techniques against systems you don't own or aren't authorized to test.
- Vaudenay, S. (2002). Security Flaws Induced by CBC Padding
- NIST SP 800-38D — GCM mode recommendation
- NIST SP 800-56C — HKDF key derivation
- Rizzo & Duong (2010). Practical Padding Oracle Attacks