Network Security
Cryptography for Networks
ONE key, used to both encrypt and decrypt.
JrCodex·9 min read
Jr Codex Computer Networks Notes
Level: Intermediate–Advanced Prerequisites: Chapter 1: The Threat Model Time to complete: ~25 minutes
Table of Contents
- Symmetric Encryption
- The Key Distribution Problem
- Asymmetric Encryption
- Hashing and MACs
- Digital Signatures
- Key Exchange and Forward Secrecy
- Summary & Next Steps
1. Symmetric Encryption
The Model
─────────────────────────────────────────
ONE key, used to both encrypt and decrypt.
ciphertext = E(key, plaintext)
plaintext = D(key, ciphertext)
Both parties must already share the key.
AES is the standard: AES-128 and AES-256, in a
mode of operation.
─────────────────────────────────────────
MODES MATTER MORE THAN KEY SIZE
─────────────────────────────────────────
ECB encrypts each block independently.
IDENTICAL PLAINTEXT BLOCKS PRODUCE
IDENTICAL CIPHERTEXT — so structure in the
data remains visible.
NEVER USE IT.
CBC chains blocks together with an IV. Better,
and needs a separate MAC for integrity, and
is vulnerable to padding oracle attacks if
implemented carelessly.
GCM AEAD — Authenticated Encryption with
Associated Data. Provides confidentiality
AND integrity in one construction, and is
parallelisable.
── the modern default
ChaCha20-Poly1305
AEAD, fast in SOFTWARE. Preferred on
devices without AES hardware acceleration.
─────────────────────────────────────────
Why AEAD Is the Right Default
─────────────────────────────────────────
Encryption alone gives CONFIDENTIALITY, not
INTEGRITY (Chapter 1). An attacker can flip bits
in ciphertext, producing predictable changes in
plaintext.
Combining encryption with a MAC by hand is
error-prone, and the order matters
(encrypt-then-MAC is correct; the alternatives
have been broken).
AEAD modes do both, correctly, in one primitive —
removing an entire class of implementation
mistakes.
─────────────────────────────────────────
Its Strength and Its Problem
─────────────────────────────────────────
FAST. AES with hardware acceleration runs at
gigabytes per second — so all BULK DATA is
encrypted symmetrically.
But it requires a SHARED KEY. Which is Section 2.
─────────────────────────────────────────
2. The Key Distribution Problem
The Difficulty
─────────────────────────────────────────
To communicate securely with a stranger, you must
first share a secret key.
To share it securely, you need a secure channel.
To have a secure channel, you need a shared key.
Circular.
─────────────────────────────────────────
And the Scaling Problem
─────────────────────────────────────────
N parties, each pair needing its own key:
N(N-1)/2 keys
10 parties ──► 45 keys
100 parties ──► 4,950
1,000 parties ──► 499,500
Every website you visit would need a pre-shared
key with you, arranged in advance.
Asymmetric cryptography solves both problems.
─────────────────────────────────────────
3. Asymmetric Encryption
The Model
─────────────────────────────────────────
TWO mathematically related keys:
PUBLIC KEY given to everyone
PRIVATE KEY never shared
Encrypt with the public key ──► only the private
key decrypts.
Sign with the private key ──► anyone verifies
with the public key.
The private key cannot feasibly be derived from
the public one — that is the whole security
assumption.
─────────────────────────────────────────
What Solves What
─────────────────────────────────────────
KEY DISTRIBUTION publish your public key
openly; no secure channel
needed
SCALING N parties need N key PAIRS, not
N²/2 shared keys
─────────────────────────────────────────
The Cost
─────────────────────────────────────────
Asymmetric operations are roughly 100-1000x
SLOWER than symmetric ones, and can only encrypt
small amounts of data.
THE HYBRID SOLUTION — used by essentially every
real system:
1. Use ASYMMETRIC crypto to establish a shared
SYMMETRIC key
2. Use SYMMETRIC crypto for all the actual data
You get asymmetric's key distribution and
symmetric's speed. This is exactly what TLS does
(Chapter 3).
─────────────────────────────────────────
The Algorithms
─────────────────────────────────────────
RSA based on the difficulty of factoring
large numbers. Needs 2048+ bit keys.
Well understood, slower, larger.
ECC elliptic curve. 256-bit ECC ≈ 3072-bit
RSA in strength.
Smaller keys, faster operations, less
bandwidth ── the modern default.
X25519 for key exchange, Ed25519 for
signatures.
─────────────────────────────────────────
4. Hashing and MACs
CRYPTOGRAPHIC HASH FUNCTIONS
─────────────────────────────────────────
Arbitrary input ──► fixed-size output.
REQUIRED PROPERTIES:
DETERMINISTIC same input, same output
FAST to compute
PREIMAGE given h(x), cannot find x
RESISTANT
SECOND PREIMAGE given x, cannot find y with
RESISTANT h(y) = h(x)
COLLISION cannot find ANY x, y with
RESISTANT h(x) = h(y)
AVALANCHE one bit changed ──► ~half the
output bits change
USE: SHA-256, SHA-3, BLAKE2.
BROKEN, DO NOT USE: MD5, SHA-1.
─────────────────────────────────────────
import hashlib, hmac, secrets
data = b"transfer 100 to account B"
print(hashlib.sha256(data).hexdigest())
# AVALANCHE: one character changes everything.
print(hashlib.sha256(b"transfer 100 to account C").hexdigest())A HASH IS NOT INTEGRITY PROTECTION
─────────────────────────────────────────
An on-path attacker who modifies your message can
simply RECOMPUTE the hash. Anyone can compute a
hash — that is the point.
To prove a message came from someone holding a
SECRET, you need a MAC.
─────────────────────────────────────────
# HMAC — a hash keyed with a shared secret.
key = secrets.token_bytes(32)
tag = hmac.new(key, data, hashlib.sha256).hexdigest()
def verify(key, data, tag):
expected = hmac.new(key, data, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, tag) # CONSTANT TIME — see below
print(verify(key, data, tag)) # True
print(verify(key, b"transfer 100 to account C", tag)) # FalseWHY compare_digest RATHER THAN ==
─────────────────────────────────────────
A normal string comparison returns as soon as it
finds a mismatched byte.
So comparing an attacker's guess takes measurably
longer when more leading bytes are correct. By
timing many attempts, an attacker recovers the
tag one byte at a time — a TIMING ATTACK.
compare_digest always examines every byte, taking
constant time.
This applies to comparing ANY secret: tokens,
password hashes, signatures. It is a small habit
that closes a real hole.
─────────────────────────────────────────
5. Digital Signatures
The Difference From a MAC
─────────────────────────────────────────
MAC a SHARED key. Either party could have
produced the tag, so it proves
authenticity between them — and
neither can prove it to a third party.
SIGNATURE the PRIVATE key signs; the PUBLIC key
verifies. Only the key holder could
have produced it.
──► NON-REPUDIATION (Chapter 1)
─────────────────────────────────────────
How It Works
─────────────────────────────────────────
SIGNING
1. hash the message
2. encrypt the hash with the PRIVATE key
VERIFYING
1. hash the received message
2. decrypt the signature with the PUBLIC key
3. compare
Hashing first means signatures are small and fast
regardless of message size — and it is why hash
collision resistance matters so much: two
messages with the same hash would share a valid
signature.
─────────────────────────────────────────
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
private = ed25519.Ed25519PrivateKey.generate()
public = private.public_key()
message = b"transfer 100 to account B"
signature = private.sign(message)
try:
public.verify(signature, message)
print("valid")
except InvalidSignature:
print("INVALID")
try:
public.verify(signature, b"transfer 900 to account B") # tampered
except InvalidSignature:
print("tampering detected")Signatures are what make certificates work — a certificate is a signed statement binding a name to a public key, which is Chapter 3.
6. Key Exchange and Forward Secrecy
DIFFIE-HELLMAN
─────────────────────────────────────────
Two parties derive a SHARED SECRET over a PUBLIC
channel, without ever transmitting it.
Public: a large prime p, a generator g
Alice picks secret a, sends A = g^a mod p
Bob picks secret b, sends B = g^b mod p
Alice computes B^a mod p = g^(ab) mod p
Bob computes A^b mod p = g^(ab) mod p
Both hold g^(ab) mod p. An eavesdropper saw
only g^a and g^b, and cannot compute g^(ab)
without solving the discrete logarithm problem.
─────────────────────────────────────────
import secrets, hashlib
p = int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD", 16) # a 1536-bit prime
g = 2
a = secrets.randbelow(p - 2) + 1 # Alice's secret, never transmitted
b = secrets.randbelow(p - 2) + 1 # Bob's secret, never transmitted
A = pow(g, a, p) # sent in the clear
B = pow(g, b, p) # sent in the clear
alice_secret = pow(B, a, p)
bob_secret = pow(A, b, p)
assert alice_secret == bob_secret # the same value, never transmitted
session_key = hashlib.sha256(str(alice_secret).encode()).digest()DH ALONE IS VULNERABLE
─────────────────────────────────────────
Diffie-Hellman gives you a shared secret with
SOMEBODY. It does not tell you WHO.
A machine-in-the-middle performs DH separately
with each party and relays between them, reading
everything.
So DH must be AUTHENTICATED — each side signs its
DH parameters with a key you can verify.
This is Chapter 1's point once more:
confidentiality without authentication is
worthless. TLS combines them (Chapter 3).
─────────────────────────────────────────
PERFECT FORWARD SECRECY
─────────────────────────────────────────
Use an EPHEMERAL DH key pair, discarded after the
session (ECDHE — the E is ephemeral).
WHY IT MATTERS:
Without PFS, an attacker who records encrypted
traffic today and obtains the server's private
key in five years can decrypt ALL of it
retroactively.
With PFS, each session's key was derived from
ephemeral values that no longer exist anywhere.
Compromising the long-term key lets an attacker
IMPERSONATE the server in future — but not read
the past.
TLS 1.3 makes PFS MANDATORY. Non-PFS key exchange
was removed from the protocol entirely, which is
one of its most important changes.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- Symmetric encryption is fast and needs a shared key; asymmetric solves key distribution and scaling but is far slower, so every real system is a hybrid.
- AEAD modes provide confidentiality and integrity in one primitive, removing a whole class of errors from combining encryption and MACs by hand.
- A hash proves nothing about origin because anyone can compute one; a MAC requires a shared secret, and a signature additionally provides non-repudiation.
- Diffie-Hellman establishes a shared secret with somebody and must be authenticated; ephemeral keys give forward secrecy, which TLS 1.3 made mandatory.
Concept Check
- Why is ECB mode unusable even though it uses the same AES cipher as GCM?
- Why does comparing a MAC with
==create a vulnerability thatcompare_digestavoids? - What exactly does perfect forward secrecy protect against that ordinary encryption does not?
Next Chapter
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index