04 β Cryptography Essentials
Level: Beginner Β· Time: ~18 min Β· Prerequisites: Lesson 3 β How the Web Works, and Where It Breaks
Why this matters
Cryptography is the only tool you have that still works when an attacker is standing between two parties. Firewalls, segmentation and monitoring all assume the network is honest; encryption does not. The catch is that nobody breaks AES β real breaches come from using crypto wrongly. People ship ECB mode, hash passwords with MD5, paste a private key into a repository, disable certificate validation "temporarily", or sign updates with a hash that has been collidable for years. The mathematics is rarely your problem. The key management always is.
The three goals
| Goal | The question it answers | Provided by |
|---|---|---|
| Confidentiality | can anyone else read this? | symmetric and asymmetric encryption |
| Integrity | has this been altered? | hashing, MACs, signatures, authenticated encryption |
| Authenticity | who really sent this? | MACs, digital signatures, certificates |
| Non-repudiation | can the sender deny it later? | signatures made with a key only they hold |
Encryption on its own gives you none of the last three. Anyone who can reach ciphertext can modify it, and a tampered ciphertext that decrypts to something plausible is a silent failure. Hence the modern rule: encrypt and authenticate together, never one without the other.
Symmetric encryption: one key, both directions
One key encrypts and decrypts. It is fast β gigabytes per second on ordinary hardware β which is why it is what actually protects your data in transit and at rest.
- Block ciphers process fixed-size blocks (AES uses 128-bit blocks) under a mode of operation that chains them. AES-128, AES-192 and AES-256 are the standards.
- Stream ciphers generate a keystream and XOR it with the data. ChaCha20 is the modern one, and it is fast on hardware without AES acceleration.
- The mode decides everything. The algorithm is usually the part that is already fine.
| Mode | What it does | Verdict |
|---|---|---|
| ECB | encrypts each block independently | never use it β identical plaintext blocks give identical ciphertext, and it has no integrity at all |
| CBC | chains blocks using the previous ciphertext | usable with a random IV and a separate MAC; padding-oracle attacks target it |
| CTR | turns a block cipher into a stream | fast, but malleable β never without a MAC |
| GCM | CTR plus an authentication tag | the default choice: authenticated encryption |
| ChaCha20-Poly1305 | stream cipher plus a Poly1305 MAC | the same guarantee, faster without AES hardware acceleration |
Authenticated encryption fails loudly when ciphertext is altered, which is exactly the behaviour you want. The unsolved problem with symmetric crypto is key distribution: two parties must share a secret, over a network an attacker can read. Asymmetric cryptography exists to solve that.
Asymmetric encryption: two keys
Each party holds a keypair β a public key anyone may have, and a private key only they hold. Encrypt with the public key and only the private key decrypts; sign with the private key and anyone can verify with the public key.
| RSA | ECC (elliptic curve) | |
|---|---|---|
| Basis | difficulty of factoring large integers | difficulty of the elliptic-curve discrete logarithm |
| Typical sizes | 2048β4096 bits | 256β384 bits |
| Equivalent strength | 2048-bit RSA β 112-bit security | 256-bit ECC β 128-bit security |
| Practical effect | larger keys and signatures, more CPU | smaller keys, less bandwidth β modern TLS, Ed25519 SSH keys, mobile, IoT |
The counterintuitive part: asymmetric crypto almost never protects your data. It is far slower than symmetric and cannot encrypt more than its key size in one operation. Its three real jobs are key exchange (agreeing a symmetric session key), digital signatures (origin and integrity), and certificates (binding a public key to a name). In a TLS session, your traffic is protected by symmetric encryption under a key that asymmetric cryptography agreed. Both halves matter; they do different work.
Hashing and password storage
A hash takes any input and produces a fixed-length digest β SHA-256 gives 256 bits whether you feed it one byte or a terabyte. It is one-way (not invertible), deterministic (same input, same output), collision resistant (two inputs with the same digest should be infeasible to find), and it has the avalanche property (one bit changed alters roughly half the output bits).
| Algorithm | Status |
|---|---|
| MD5 | broken β practical collisions for years; never use it for anything security-relevant |
| SHA-1 | broken β chosen-prefix collisions are practical; deprecated everywhere |
| SHA-256 / SHA-512 | fine β the default for integrity and signatures |
| SHA-3 | fine β a different internal design (Keccak), useful for diversity and for resisting length-extension |
Hashing is not encryption. Encryption is reversible with a key; hashing is not reversible at all. You encrypt data you must read later; you hash data you only need to compare. And because a hash is deterministic, a hash of a password is not a secret β anyone with a wordlist can hash each candidate and compare.
| Password storage mistake | Why it is fatal |
|---|---|
| Plaintext | one leak compromises every account, including re-used ones elsewhere |
| A fast hash (MD5, SHA-256) | commodity GPUs compute billions per second |
| One hash for everyone | identical passwords give identical hashes; cracking one reveals them all |
| Encryption instead of hashing | the key sits beside the database, so the leak includes the key |
Correct design has three parts: a unique random salt per user (stored alongside, need not be secret β it defeats precomputed tables), a deliberately slow algorithm (bcrypt, scrypt, and memory-hard Argon2 exist for exactly this), and a work factor you raise over time so the same attack never gets cheaper.
import hashlib, os, hmac
salt = os.urandom(16) # unique per user, stored with the hash
dk = hashlib.scrypt(b"correct horse battery staple", salt=salt,
n=2**14, r=8, p=1, dklen=32)
candidate = hashlib.scrypt(b"correct horse battery staple", salt=salt,
n=2**14, r=8, p=1, dklen=32)
print(hmac.compare_digest(candidate, dk)) # constant-time compare, never ==
Use a maintained library (Argon2id or bcrypt) in real applications rather than assembling primitives yourself.
MACs, signatures and PKI: proving origin
A message authentication code is a short tag computed from a message and a shared secret key. Anyone with the key can verify it; nobody without the key can forge it. HMAC is the standard construction β and sha256(secret + message) is not a substitute, because length-extension attacks make it forgeable.
An HMAC proves integrity and authenticity between two parties who share the key β it cannot prove which of them sent it, because both can compute the tag. A digital signature proves more: signing uses a private key and verification uses the public key, so only one party could have produced it. That is what gives you non-repudiation, and it is why signatures underpin certificates, signed updates and signed code.
A public key is just a number; nothing in it says it belongs to your bank. PKI fixes that with a hierarchy of trust:
- A certificate authority (CA) signs a certificate binding a public key to a name.
- Root CAs are trusted because they ship inside your OS and browser; they are kept offline and used sparingly.
- Intermediate CAs are signed by a root and do the day-to-day signing, limiting the blast radius if one is compromised.
- Your client trusts the chain: leaf β intermediate β root, all signatures valid, name matching.
You obtain a certificate by generating a keypair and sending a certificate signing request (CSR) β your public key, your name, and a signature proving you hold the private key. The CA validates the name and returns a signed certificate.
| Inside an X.509 certificate | Why it matters |
|---|---|
| Subject | the entity β organisation and common name |
| Subject Alternative Name (SAN) | the hostnames it is valid for; modern browsers ignore the common name entirely |
| Issuer | which CA signed it β the first link in the chain |
| Validity period | not-before and not-after; expiry is an outage, and short lifetimes are the direction of travel |
| Public key | the point of the whole exercise |
| Serial / thumbprint | identifies this exact certificate, for revocation and pinning |
| Extensions | key usage, basic constraints, OCSP and CRL endpoints |
Revocation is the weak spot. A CRL is a list of revoked serials, often stale and large. OCSP asks a responder about one certificate β faster, but a privacy leak and a dependency; OCSP stapling lets the server supply a fresh signed answer instead. Self-signed certificates are a legitimate answer for internal services, lab equipment and management interfaces, provided the certificate is installed in the clients' trust store and the name matches. What is never acceptable is training users to click through the browser warning, because that is what a real man-in-the-middle attack looks like.
TLS, key management and the mistakes that break crypto
The TLS 1.3 handshake, in order:
- The client sends
ClientHello: supported versions, cipher suites, a key share, a random value. - The server replies
ServerHellowith its chosen parameters and its own key share, then sends its certificate and signs the handshake transcript to prove it holds the private key. - Both sides derive session keys from the key shares via a Diffie-Hellman exchange β the session key is never transmitted.
- The client validates the chain and the signature against its trust store and the requested hostname.
- Encrypted application data flows both ways.
What changed from TLS 1.2: one round trip instead of two; obsolete cryptography (static RSA key exchange, CBC ciphers, RC4) removed from the protocol entirely; forward secrecy by default, so recording today's traffic does not help an attacker who steals the server key tomorrow (under 1.2 a static-RSA suite allowed exactly that); and most of the handshake, including the certificate, is encrypted, so a passive observer sees far less. Negotiate 1.3, allow 1.2 as a floor with a modern cipher list, and disable 1.0 and 1.1.
Key management is where crypto actually fails:
- Keys live outside the code. A hardcoded key is in your source control, your backups and every developer's laptop. Use a secrets manager, a KMS or a hardware module (HSM/TPM) for anything that matters.
- Rotate on a schedule and on suspicion. Rotation limits the value of a leak and forces you to prove you can do it β the part that always breaks mid-incident.
- Least privilege on keys. The application that verifies signatures should not be able to sign.
- Nothing secret in git β not a config file, not a test fixture, not a comment, not the history. Once pushed, assume it is public, and rotate.
| Mistake | Correct answer |
|---|---|
| ECB mode | GCM or ChaCha20-Poly1305 β authenticated encryption |
| MD5 or SHA-1 for integrity or signatures | SHA-256, SHA-512 or SHA-3 |
| Unsalted or fast password hashing | Argon2id, scrypt or bcrypt, per-user salt, tuned work factor |
| Hardcoded key or password in source | secrets manager, KMS, injected at runtime, rotated |
| Updates verified with a weak hash | signatures verified against an offline public key |
| TLS 1.0/1.1 still enabled | TLS 1.3 preferred, 1.2 with modern ciphers as the floor |
| Certificate errors ignored in code | fail closed; fix the trust store, never disable validation |
| Home-grown algorithm | a vetted library and a standard construction |
| One key for encryption and signing | separate keys per purpose |
| Certificates with no renewal plan | automated renewal plus expiry monitoring |
Practical: inspect a certificate, compute a hash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
sha256sum firmware.bin # verify against the published value
echo -n "hello" | openssl dgst -sha256 # a hash of a short string
openssl rand -hex 32 # random data, done properly
openssl req -new -newkey rsa:3072 -nodes -keyout internal.key -out internal.csr
openssl req -x509 -days 365 -key internal.key -in internal.csr -out internal.crt
Read s_client output slowly: protocol version, cipher suite, chain and validity dates are the four facts you need when a TLS problem lands on your desk.
Attack it / Defend it
| The attack | How it works | The control that stops it |
|---|---|---|
| Password cracking | harvest a hash dump and test guesses at GPU speed | salted, slow, memory-hard hashing |
| Collision attack on a weak hash | craft a second file with the same MD5 or SHA-1 digest | SHA-256 or better, plus signed release artefacts |
| Man-in-the-middle with a forged certificate | present a self-signed or wrong-name certificate and hope the user proceeds | certificate validation enforced in code, HSTS, awareness |
| SSL stripping | downgrade the connection before TLS begins | HSTS, HTTPS-only, no mixed content |
| Downgrade to weak TLS parameters | negotiate the weakest option both sides accept | TLS 1.3, modern cipher list, old versions disabled server-side |
| Padding-oracle attack | use error messages to decrypt CBC ciphertext byte by byte | authenticated encryption that validates before decrypting |
| Key theft from source or backups | find the key in a repository, image or backup | secrets managers, KMS/HSM, secret scanning of commit history |
| Data at rest on a stolen laptop | remove the disk and read it elsewhere | full-disk encryption with the key sealed to a TPM plus a passphrase |
| Forged firmware or update | serve a malicious update the device accepts | signed updates verified against an offline key, Secure Boot |
| Captured traffic decrypted later | record now, steal the private key later | forward secrecy (the TLS 1.3 default), short-lived certificates |
Key takeaways
- Encryption without authentication is a bug, not a smaller feature β use authenticated modes.
- Asymmetric crypto is for key exchange and signatures, not bulk data; the slowness is why TLS is a hybrid.
- A hash of a password is not safe unless it is salted, slow and memory-hard β fast hashes are for integrity, not secrets.
- Certificates move the trust problem rather than deleting it: you are trusting a CA hierarchy, so keep the chain short and validation strict.
- Real crypto failures are operational β hardcoded keys, keys nobody rotates, warnings users click through, protocols left enabled.
Check yourself
- Why is ECB mode unusable, and what do GCM and ChaCha20-Poly1305 give you that it does not?
- Why is a certificate signed with SHA-1 a problem even if that certificate has not expired?
- Explain why TLS starts with asymmetric cryptography and then switches to symmetric for the data.
- What does a digital signature prove that an HMAC cannot, and what must the verifier hold?
- You find the same password hash for two different users. What went wrong?