Chapter 21

Cryptography

College
At a glance
Core ideaSecurity rests on problems easy to verify but hard to solve.
Key termPublic-key (RSA) — encrypt with a public key, decrypt with a private one.
You can…Implement toy RSA end-to-end with modular arithmetic.
Watch outSmall keys are trivially factored — real RSA uses enormous primes.
Theory

Cryptography is the mathematics of keeping information secret and authentic even when adversaries can see the ciphertext. A symmetric cipher uses the same secret key to encrypt and decrypt — fast, but both parties must already share the key secretly. An asymmetric (public-key) cipher uses a pair of keys — a public key anyone may know, and a private key only the owner holds — solving the problem of how two strangers agree on a secret over an insecure channel in the first place.

The best-known public-key system, RSA (Rivest–Shamir–Adleman, 1977), rests on modular arithmetic and a specific hardness assumption: multiplying two large prime numbers is fast, but factoring their product back into those primes is believed to be computationally infeasible for large enough numbers — exactly the "easy to verify, hard to solve" asymmetry from Chapter 19. Anyone can encrypt with your public key; only someone who knows the prime factorisation (your private key) can efficiently decrypt.

Explanation

RSA in outline: pick two large primes p and q, compute n = p·q (the public modulus) and a public exponent e. The private key d is computed so that (mᵉ)ᵈ ≡ m (mod n) for any message m — encryption raises to the power e, decryption raises to the power d, and the modular arithmetic returns exactly the original message, thanks to a classical result (Euler's theorem) about exponents modulo n. Security hinges entirely on n being hard to factor: with today's algorithms and hardware, factoring a 2048-bit RSA modulus would take far longer than the age of the universe, while multiplying the two known primes to create it took microseconds.

This is why P vs NP is not a side note — if a fast, polynomial-time factoring algorithm were ever found for classical computers (and quantum computers already threaten this via Shor's algorithm), RSA-based security would collapse overnight. Modern systems increasingly hedge with "post-quantum" schemes based on different hard problems, such as lattice problems, precisely because of this risk.

From Stage 2 to here. The shift cipher you cracked as a child (Chapter 5) is breakable in seconds because it has only 25 possible keys — brute force wins instantly. RSA is secure not because its idea is more complicated, but because its key space and the hardness of factoring make brute force genuinely, provably, astronomically infeasible.

Practical

We implement RSA with deliberately small, textbook-sized primes so every step is inspectable — never use numbers this small for anything real, but the mathematics is identical to production-grade RSA.

import math

def egcd(a, b):                              # extended Euclid: finds gcd and modular inverse
    if b == 0:
        return a, 1, 0
    g, x1, y1 = egcd(b, a % b)
    return g, y1, x1 - (a // b) * y1

def modinv(e, phi):
    g, x, _ = egcd(e, phi)
    if g != 1:
        raise ValueError("e has no inverse mod phi")
    return x % phi

def generate_keys(p, q, e=17):
    n = p * q
    phi = (p - 1) * (q - 1)                  # Euler's totient of n
    d = modinv(e, phi)                       # private exponent: e*d = 1 (mod phi)
    return (e, n), (d, n)                    # (public key, private key)

def encrypt(m, public_key):
    e, n = public_key
    return pow(m, e, n)                       # ciphertext = m^e mod n

def decrypt(c, private_key):
    d, n = private_key
    return pow(c, d, n)                       # plaintext  = c^d mod n

# Toy primes (real RSA uses primes hundreds of digits long!)
p, q = 61, 53
public_key, private_key = generate_keys(p, q)

message = 42                                  # a number standing in for our secret
ciphertext = encrypt(message, public_key)
recovered  = decrypt(ciphertext, private_key)

print("public key: ", public_key)
print("ciphertext: ", ciphertext)
print("recovered:  ", recovered)              # -> 42, exactly

# Breaking it requires factoring n back into p and q -- trivial here (n=3233),
# effectively impossible once p and q are hundreds of digits long.
n = public_key[1]
print("factoring n =", n, "by brute force:",
      [i for i in range(2, int(math.isqrt(n)) + 1) if n % i == 0][0], "...")

The entire security argument is compressed into that last line: with toy numbers, brute-force factoring finds p almost instantly. Real RSA uses primes so large that the identical brute-force loop would not finish before the sun burns out — the algorithm hasn't changed, only the size of the haystack.

Q&A
Q1 Why is symmetric encryption still used if asymmetric solves key sharing?

Asymmetric encryption is far more computationally expensive. Real systems like HTTPS use asymmetric crypto only to safely exchange a symmetric session key, then switch to fast symmetric encryption for the actual data — a "hybrid" approach that gets both security and speed.

Q2 What would happen to RSA if someone found a fast factoring algorithm?

Every RSA-protected system would become breakable almost immediately. This is exactly why cryptographers watch complexity theory research so closely, and why quantum computing — via Shor's algorithm, which factors efficiently on a quantum computer — is taken seriously as a long-term threat.

Q3 Why must p and q be prime, not just any two numbers?

Euler's totient formula, phi = (p-1)(q-1), only holds cleanly when p and q are prime; using composite numbers breaks the mathematical guarantee that encryption and decryption are true inverses of each other, and also makes n far easier to factor.

Q4 Is a longer key just "a little more secure"?

No — security grows roughly exponentially with key length because factoring difficulty grows exponentially, while encryption and decryption cost only grows polynomially. Doubling key length can mean the difference between "breakable in seconds" and "unbreakable for millennia," at only a small, tolerable cost to legitimate users.

Concept mind map

How the ideas connect

Every key idea in this chapter, branching from the core concept — use it to see the whole picture at a glance.

plaintextciphertextkeypublic-key RSAmodular arithmeticeasy to verifyCryptography
Infographic

The process, step by step

Step 1Key generationPick primes p, q; compute n = p*q and public/private exponents.
Step 2PublishShare the public key (n, e); keep the private key (d) secret.
Step 3EncryptSender computes c = m^e mod n using the public key.
Step 4DecryptReceiver computes m = c^d mod n with the private key.
Solved examples

Worked problems, step by step

Follow each solution line by line, then try to reproduce it on paper before moving on.

Example 1Compute 7 mod 12, then (7 + 8) mod 12.

  1. 7 is less than 12, so 7 mod 12 = 7.
  2. 7 + 8 = 15; 15 - 12 = 3.

Example 2In toy RSA with n = 33, e = 3, encrypt message m = 2. c = m^e mod n.

  1. 2^3 = 8.
  2. 8 mod 33 = 8.
Practice problem set

Now you try

Work each one out first, then tap to reveal the worked answer.

1What is the core idea of modern cryptography?
Security rests on problems that are easy to verify but computationally hard to solve, like factoring large numbers.
2What is public-key cryptography?
A scheme with a public key to encrypt and a separate private key to decrypt, so no shared secret is needed first.
3Why is RSA secure?
Recovering the private key requires factoring the large product n = p*q, which is infeasible for big primes.
4What is plaintext vs ciphertext?
Plaintext is the readable message; ciphertext is its scrambled, encrypted form.
5What is modular arithmetic?
Arithmetic that wraps around a modulus, like a clock, where numbers reset after reaching n.
6Why keep the private key secret?
Anyone holding it can decrypt messages or forge signatures, breaking the system security.