Skip to content

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.

import hmac, hashlib
tag = hmac.new(b"shared-key", b"transfer=100", hashlib.sha256).hexdigest()

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:

  1. A certificate authority (CA) signs a certificate binding a public key to a name.
  2. Root CAs are trusted because they ship inside your OS and browser; they are kept offline and used sparingly.
  3. Intermediate CAs are signed by a root and do the day-to-day signing, limiting the blast radius if one is compromised.
  4. 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:

  1. The client sends ClientHello: supported versions, cipher suites, a key share, a random value.
  2. The server replies ServerHello with its chosen parameters and its own key share, then sends its certificate and signs the handshake transcript to prove it holds the private key.
  3. Both sides derive session keys from the key shares via a Diffie-Hellman exchange β€” the session key is never transmitted.
  4. The client validates the chain and the signature against its trust store and the requested hostname.
  5. 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

  1. Why is ECB mode unusable, and what do GCM and ChaCha20-Poly1305 give you that it does not?
  2. Why is a certificate signed with SHA-1 a problem even if that certificate has not expired?
  3. Explain why TLS starts with asymmetric cryptography and then switches to symmetric for the data.
  4. What does a digital signature prove that an HMAC cannot, and what must the verifier hold?
  5. You find the same password hash for two different users. What went wrong?

Next

Lesson 5 β€” Identity, Authentication and Authorisation