A fully self-contained Remote Procedure Call (RPC) framework built on raw TCP sockets — with a secure variant that adds AES-128-CTR encryption, HMAC-SHA256 message authentication, and nonce-based replay protection. Designed to illustrate both the mechanics of RPC and the consequences of skipping security.
- Newline-delimited JSON wire protocol over raw TCP sockets
RPCServer— registers Python functions as remote procedures; one thread per clientRPCClient— synchronous call interface with a magicproxyobject that makes remote calls look local- Supports both positional (
[a, b]) and keyword ({"a": 1}) parameter passing - Typed error codes:
404 METHOD_NOT_FOUND,400 INVALID_PARAMS,500 INTERNAL_ERROR,408 TIMEOUT,401 UNAUTHORIZED
Three layered protections on top of the core transport:
| Layer | Mechanism | What it prevents |
|---|---|---|
| Confidentiality | AES-128-CTR encryption | Eavesdropping — attackers see only ciphertext |
| Integrity + Auth | HMAC-SHA256 (constant-time) | Tampering — any modification is detected |
| Replay protection | Nonce + 30-second timestamp window | Replay attacks — captured requests are rejected |
Zero external dependencies for the crypto fallback — uses Python's
hmac/hashlibifcryptographyis unavailable.
Live, runnable simulations of three classic network attacks against the insecure RPC:
- Eavesdropping — A transparent TCP proxy logs every JSON message, exposing credentials and data in plaintext
- Replay Attack — A captured
auth.loginrequest is replayed verbatim; the server accepts it without question - Response Tampering — An active MITM proxy doubles all numeric results; the client has no way to detect it
Each attack is followed by a countermeasure summary pointing to the secure implementation.
19 pre-registered remote procedures across five namespaces:
| Namespace | Procedures |
|---|---|
math.* |
add, subtract, multiply, divide, sqrt, factorial |
str.* |
reverse, uppercase, word_count, caesar |
kv.* |
set, get, delete, list |
auth.* |
login, whoami |
sys.* |
time, ping, info |
rpc_project/
├── server.py # Standalone server entry point (secure or insecure)
├── client.py # Interactive REPL client
├── main.py # Placeholder entry point
│
├── core/
│ └── rpc_core.py # Transport, RPCServer, RPCClient, wire protocol
│
├── secure/
│ └── secure_rpc.py # SecureRPCServer, SecureRPCClient, crypto primitives
│
├── services/
│ └── demo_services.py # 19 demo remote procedures (math, str, kv, auth, sys)
│
├── attacks/
│ ├── attack_demo.py # EavesdropProxy, ReplayAttacker, TamperingProxy
│ ├── verify_attacks.py # Automated verification of attack outcomes
│ └── QUICK_REFERENCE.md # Attack cheat-sheet
│
└── demo/
└── run_demo.py # Self-contained end-to-end walkthrough
- Python ≥ 3.10
- Install dependencies:
pip install -r requirements.txt
# or, with uv:
uv sync
requirements.txtlistscryptography>=41.0.0(for AES-CTR) andcolorama>=0.4.6. The fallback HMAC-based stream cipher is used automatically ifcryptographyis absent.
# Secure mode (default) — AES + HMAC on port 9000
python server.py
# Insecure mode (plaintext)
python server.py --insecure
# Custom host, port, and shared secret
python server.py --host 0.0.0.0 --port 9999 --key my_shared_secret# In a separate terminal
python client.py
# Options
python client.py --insecure # Match insecure server
python client.py --host 192.168.1.5 --port 9999 # Remote server
python client.py --key my_shared_secret # Custom key (must match server)The REPL accepts commands of the form <method> [arg1] [arg2] ...:
rpc(secure)> math.add 10 32
✓ 42 (1.3 ms)
rpc(secure)> auth.login alice password123
✓ {"status": "ok", "token": "alice:1700000000", "user": "alice"} (2.1 ms)
rpc(secure)> str.caesar "Hello World" 13
✓ Uryyb Jbeyq (0.9 ms)
rpc(secure)> sys.info
✓ {"os": "Darwin", "python": "3.12.0", "pid": 12345, "uptime": ...}
rpc(secure)> list # show all available methods
rpc(secure)> help # show usage examples
rpc(secure)> quit
# All three attacks against the insecure server
python -m attacks.attack_demo
# Or run the secure vs insecure comparison
python -m secure.secure_rpc
# Automated verification suite
python -m attacks.verify_attackspython -m demo.run_demoAll messages are newline-delimited JSON sent over a persistent TCP connection.
Insecure (plain)
// Request
{"id": 1, "method": "math.add", "params": [2, 3], "ts": 1700000000.0}
// Response
{"id": 1, "result": 5, "error": null, "ts": 1700000000.1}
// Error
{"id": 1, "result": null, "error": {"code": 404, "msg": "Method 'foo' not found"}, "ts": ...}Secure (encrypted envelope)
{
"iv": "<hex: 16-byte AES IV>",
"ct": "<hex: AES-CTR ciphertext of the original JSON>",
"hmac": "<hex: HMAC-SHA256(iv + ct, mac_key)>",
"ts": 1700000000.0
}The ts field is unencrypted to allow the server's replay pre-check before decryption.
from core.rpc_core import RPCServer, RPCClient
# Server
server = RPCServer(host='0.0.0.0', port=9000)
@server.register()
def add(a, b):
return a + b
@server.register(name='math.power')
def power(base, exp):
return base ** exp
server.start(blocking=False)
# Client — explicit call
with RPCClient('127.0.0.1', 9000) as client:
result = client.call('add', [10, 32]) # → 42
# Client — proxy (looks like a local function call)
with RPCClient('127.0.0.1', 9000) as client:
result = client.proxy.add(10, 32) # → 42Secure variant:
from secure.secure_rpc import SecureRPCServer, SecureRPCClient
SECRET = b'my_shared_secret_32b'
server = SecureRPCServer(host='0.0.0.0', port=9100, secret_key=SECRET)
@server.register()
def greet(name):
return f"Hello, {name}!"
server.start(blocking=False)
with SecureRPCClient('127.0.0.1', 9100, secret_key=SECRET) as client:
print(client.proxy.greet('World')) # Hello, World!Attack Insecure RPC Secure RPC
──────────────────── ────────────────── ──────────────────
Eavesdropping Vulnerable ✗ Protected ✓
Response Tampering Vulnerable ✗ Detected ✓
Replay Attack Vulnerable ✗ Rejected ✓
Credential Theft Vulnerable ✗ Encrypted ✓
Key derivation separates encryption and MAC keys from the single shared secret:
enc_key = SHA-256(secret || "enc")[:16] # 16-byte AES-128 key
mac_key = SHA-256(secret || "mac") # 32-byte HMAC-SHA256 key
| Requirement | Version |
|---|---|
| Python | ≥ 3.10 |
| cryptography | ≥ 41.0.0 (optional — fallback available) |
| colorama | ≥ 0.4.6 |
No framework dependencies. The RPC core uses only the Python standard library (socket, threading, json, hmac, hashlib).
This project is licensed under the MIT License