Learn › Security
·FREE·PAPER·20 min read

RSA from Scratch — The Number Theory Behind Public Keys

RSA lets you publish a key that locks but cannot unlock. This builds it from congruence arithmetic up: key generation, the two modular exponentiations, and a line-by-line proof from Euler's theorem of why the message comes back — then a worked example small enough to check by hand, why factoring is the security assumption, and why textbook RSA must never be shipped without padding.

Key points

  • Public-key cryptography gives everyone a public key and a private key: anyone can encrypt with the public key, and only the private key can decrypt.
  • RSA rests on an asymmetry: multiplying two large primes is easy, but no efficient algorithm is known to factor their product.
  • RSA encrypts with c ≡ m^e mod n and decrypts with m ≡ c^d mod n; the message returns because ed = 1 + kφ(n) and Euler's theorem.
  • Finding φ(n) is equivalent to factoring, and 2048 bits is the current floor, but Shor's algorithm factors integers in polynomial time on a large enough quantum computer.
  • Textbook RSA is deterministic, malleable, and leaks messages under small exponents, so padding is required: RSAES-OAEP for encryption and RSASSA-PSS for signatures.
ModalitytextTaskmath

"Rivest

Primary source — what this article is built on

undefined2026-09-23

"Rivest"Rivest
A Method for Obtaining Digital Signatures and Public-Key CryptosystemsShamir & Adleman 1978
https://dl.acm.org/doi/10.1145/359340.359342"CACM 21(2):120-126
PKCS #1: RSA Cryptography Specifications Version 2.2RFC 8017Paper page

The problem: agreeing on a secret you never shared

For most of history, encryption meant both parties knew the same secret. You and I agree on a key in advance, I scramble a message with it, you unscramble it with the same key. This is symmetric encryption, and it is fast and well understood — but it has a bootstrapping problem that looks unsolvable. To talk securely, we must first meet securely to exchange the key. On the internet, where you want to buy something from a shop you have never contacted before, there is no such prior meeting.

Public-key cryptography breaks that circle with an idea that sounds impossible on first hearing: a key that can lock but not unlock. Everyone gets a public key they publish to the world and a private key they never reveal. Anyone can use your public key to encrypt a message to you; only your private key can decrypt it. No prior meeting, no shared secret.

Rivest, Shamir and Adleman published the first practical construction of this in 1978, and RSA is still the name on it. It is built out of ordinary integer arithmetic — no exotic mathematics — and this article assembles it from the ground up: the arithmetic you need, the key generation, the two operations, a proof that they undo each other, a small worked example, and the reasons the textbook version must be padded before it touches real traffic. (For where RSA sits among the other tools, see Cryptography from Scratch.)

Prerequisite: arithmetic on a clock

Everything in RSA happens modulo some integer nn, which means: do the ordinary arithmetic, divide by nn, and keep only the remainder. Clock arithmetic is the familiar case with n=12n = 12: four hours after ten o'clock is two o'clock, because 1414 divided by 1212 leaves remainder 22.

The notation for "aa and bb leave the same remainder when divided by nn" is

a≡b(modn)a \equiv b \pmod{n}

read "aa is congruent to bb modulo nn." In words: if you only look at the remainder after dividing by nn, you cannot tell aa and bb apart. So 14≡2(mod12)14 \equiv 2 \pmod{12}. Three facts about congruences are all we need, and all three are easy to believe:

  1. Addition and multiplication survive. If a≡a′a \equiv a' and b≡b′b \equiv b', then a+b≡a′+b′a + b \equiv a' + b' and ab≡a′b′ab \equiv a'b'. You may reduce mod nn at any point in a calculation without changing the answer — which is what makes working with enormous numbers tractable at all.
  2. Exponentiation is therefore cheap. a2k=(ak)2a^{2k} = (a^k)^2, so you can square repeatedly and reduce mod nn after every step. Computing a2048 mod na^{2048} \bmod n takes about 11 squarings, not 2048 multiplications. This is modular exponentiation by squaring, and it is why RSA is usable with 2048-bit numbers.
  3. Division is the special one. There is no general "divide by bb." What sometimes exists is a modular inverse: a number b−1b^{-1} with b⋅b−1≡1(modn)b \cdot b^{-1} \equiv 1 \pmod{n}. It exists exactly when bb and nn share no common factor besides 1, and the extended Euclidean algorithm finds it quickly.

One more object. Euler's totient function φ(n)\varphi(n) counts how many integers in 1,2,…,n1, 2, \dots, n share no common factor with nn. For a prime pp, every one of 1,…,p−11, \dots, p-1 qualifies, so φ(p)=p−1\varphi(p) = p - 1. For a product of two distinct primes — the only case RSA needs —

φ(pq)=(p−1)(q−1)\varphi(pq) = (p-1)(q-1)

which you can see by counting what gets excluded from 1,…,pq1, \dots, pq: the qq multiples of pp, the pp multiples of qq, with pqpq itself double-counted, giving pq−q−p+1=(p−1)(q−1)pq - q - p + 1 = (p-1)(q-1). In words: strike out the multiples of pp and the multiples of qq, and exactly (p−1)(q−1)(p-1)(q-1) numbers are left.

The asymmetry RSA is built on

A public-key scheme needs an operation that is easy to do and hard to undo. RSA's is multiplication of primes.

Multiplying two 1024-bit primes together takes a computer no measurable time. Going the other way — given only the 2048-bit product, recover the two primes — is integer factorization, and no efficient algorithm for it is known. The best published classical method, the general number field sieve, runs in sub-exponential but still hopeless time at that size: the largest RSA modulus factored in public, the 829-bit RSA-250, took about 2700 core-years in 2020. 2048 bits is far beyond that.

So: the product is public, the factors are private. The rest of RSA is machinery for turning that gap into an encryption scheme.

Key generation

Five steps, each of which we now have the tools for.

1. Pick two large distinct primes pp and qq. In practice, generate random odd numbers of the right size and test them for primality (Miller–Rabin) until two pass. For a 2048-bit key, each is about 1024 bits.

2. Compute the modulus n=pqn = pq. This number is public. Its size is what people mean by "a 2048-bit RSA key."

3. Compute φ(n)=(p−1)(q−1)\varphi(n) = (p-1)(q-1). This number must stay secret — and note that anyone who can factor nn can compute it, which is the whole crux of the security.

4. Choose the public exponent ee with 1<e<φ(n)1 < e < \varphi(n) and gcd⁡(e,φ(n))=1\gcd(e, \varphi(n)) = 1. The coprimality requirement is not aesthetic: it is exactly the condition for step 5 to have a solution. Nearly all real keys use e=65537=216+1e = 65537 = 2^{16} + 1, which is prime, and whose binary form 10000000000000001 has only two 1-bits — so encryption costs just 17 squarings and one multiply.

5. Compute the private exponent dd as the modular inverse of ee:

d≡e−1(modφ(n)),equivalentlyed≡1(modφ(n))d \equiv e^{-1} \pmod{\varphi(n)}, \quad\text{equivalently}\quad ed \equiv 1 \pmod{\varphi(n)}

Unpacking the "equivalently": dd is the number that, multiplied by ee, lands exactly one step past a multiple of φ(n)\varphi(n). Written without congruences, there is some integer kk with ed=1+kφ(n)ed = 1 + k\varphi(n) — and it is this form we will need in the proof. The extended Euclidean algorithm produces dd from ee and φ(n)\varphi(n) in microseconds. In words, dd is the exponent that exactly cancels out raising to the ee.

The result is two keys:

Encryption and decryption

Represent the message as an integer mm with 0≤m<n0 \le m < n. (How you get from bytes to an integer is where padding lives; that comes later.) Then both directions are a single modular exponentiation:

encrypt:c≡me(modn)\textbf{encrypt:}\quad c \equiv m^{e} \pmod{n}

decrypt:m≡cd(modn)\textbf{decrypt:}\quad m \equiv c^{d} \pmod{n}

That is the entire algorithm. In words: encryption and decryption are the same operation — raise to a power, keep the remainder mod nn — and only the exponent differs. Raise to the public exponent to lock, raise to the private exponent to unlock, and reduce mod nn throughout. The asymmetry between the two operations is nothing but the asymmetry between knowing dd and not knowing it.

The obvious question is why the second undoes the first.

Why the message comes back

The engine is a theorem of Euler, stated here and then used once.

Euler's theorem. If gcd⁡(a,n)=1\gcd(a, n) = 1, then aφ(n)≡1(modn)a^{\varphi(n)} \equiv 1 \pmod{n}.

In words: raise any number coprime to nn to the power φ(n)\varphi(n), and modulo nn you land exactly on 1. (Fermat's little theorem, ap−1≡1(modp)a^{p-1} \equiv 1 \pmod p for prime pp, is the special case n=pn = p.) What this gives us is a way to make exponents wrap around: multiplying by aφ(n)a^{\varphi(n)} changes nothing mod nn, so exponents effectively live mod φ(n)\varphi(n). That is precisely why dd was defined mod φ(n)\varphi(n) and not mod nn.

Now decrypt, one line at a time. Start from the ciphertext and substitute:

cd≡(me)d(modn)c^{d} \equiv (m^{e})^{d} \pmod{n}

Because c≡mec \equiv m^e by definition of encryption.

(me)d=med(m^{e})^{d} = m^{ed}

Because a power of a power multiplies the exponents — no modular arithmetic involved yet.

med=m1+kφ(n)m^{ed} = m^{1 + k\varphi(n)}

Because step 5 of key generation gave us exactly ed=1+kφ(n)ed = 1 + k\varphi(n) for some integer kk. This is the step the whole construction was engineered to make available.

m1+kφ(n)=m1⋅(mφ(n))km^{1 + k\varphi(n)} = m^{1} \cdot \left(m^{\varphi(n)}\right)^{k}

Because xa+b=xaxbx^{a+b} = x^a x^b, and then xkφ(n)=(xφ(n))kx^{k\varphi(n)} = (x^{\varphi(n)})^k — still just exponent rules.

m⋅(mφ(n))k≡m⋅1k(modn)m \cdot \left(m^{\varphi(n)}\right)^{k} \equiv m \cdot 1^{k} \pmod{n}

Because Euler's theorem replaces mφ(n)m^{\varphi(n)} with 1, provided gcd⁡(m,n)=1\gcd(m, n) = 1.

m⋅1k=mm \cdot 1^{k} = m

Because 1 to any power is 1. So cd≡m(modn)c^d \equiv m \pmod n, and since we required 0≤m<n0 \le m < n, the residue we get back is mm rather than something congruent to it. The message returns. In words, the six lines say: raise to ee, then to dd, and the exponent goes kk full laps of φ(n)\varphi(n) and lands back at 1.

Notice what did the work: the whole scheme is an exercise in choosing dd so that eded is one more than a multiple of φ(n)\varphi(n), because that is the exponent Euler's theorem collapses. And φ(n)\varphi(n) is computable from pp and qq — which is why the factors must stay secret.

One honest caveat, since the proof above has a gap. Euler's theorem needed gcd⁡(m,n)=1\gcd(m, n) = 1, and a message could in principle be a multiple of pp or qq. RSA does still decrypt correctly in those cases; the argument uses the Chinese Remainder Theorem to handle each prime factor separately rather than Euler's theorem on nn directly. For random messages the odds of hitting such an mm are about 1/p+1/q1/p + 1/q — astronomically small at real key sizes, and irrelevant in practice because padding randomizes mm anyway. The gap is worth knowing about so you are not surprised when a rigorous treatment spends a page on it.

A worked example you can check by hand

Real keys use 617-digit numbers. Take tiny primes instead so every step is verifiable with a pencil.

Key generation. Let p=3p = 3, q=11q = 11.

So the public key is (33,3)(33, 3) and the private key is (33,7)(33, 7). Note ed=21=1+kφ(n)ed = 21 = 1 + k\varphi(n) with k=1k = 1 — the exact form the proof needed.

Encrypt m=5m = 5.

c≡53=125(mod33)c \equiv 5^{3} = 125 \pmod{33}

125=3×33+26125 = 3 \times 33 + 26, so c=26c = 26. In words, the ciphertext is nothing more than "5 multiplied by itself three times, remainder after dividing by 33."

Decrypt. We need 267 mod 3326^{7} \bmod 33, computed by repeated squaring, reducing at every step:

We get 5 back. Every number here was small enough to check, and the same six lines with 2048-bit integers are what your browser runs.

Two things this toy example also makes visible. First, mm must be less than nn: with n=33n = 33 there are only 33 possible messages, and m=40m = 40 would come back as 40−33=740 - 33 = 7. Second, the encryption is deterministic — 5 always maps to 26 — and with only 33 possibilities an attacker simply encrypts all of them and looks up the answer. That is not an artifact of the small size, as the next-but-one section explains.

Why it is hard to break

The attacker sees (n,e)(n, e) and cc and wants mm. Consider the routes.

Factor nn. With pp and qq in hand, compute φ(n)=(p−1)(q−1)\varphi(n) = (p-1)(q-1), run the extended Euclidean algorithm to get dd, decrypt. This is the primary attack and it is why RSA's security rests on the hardness of factoring. At 2048 bits it is out of reach of all known classical methods — the general number field sieve's cost grows sub-exponentially in the number of digits, and the 829-bit public record already cost thousands of core-years.

FIG 1Read the horizontal axis as key size in bits. Multiplying p × q grows only polynomially in the bit length (the n² curve), while naive trial division grows exponentially (the 2ⁿ side). The best method, the general number field sieve, sits in between at sub-exponential. Switch to the log scale and watch the gap between the easy direction and the hard one open up by orders of magnitude

Compute φ(n)\varphi(n) some other way. No help: knowing both n=pqn = pq and φ(n)=(p−1)(q−1)=n−p−q+1\varphi(n) = (p-1)(q-1) = n - p - q + 1 gives you p+qp + q, and from a sum and a product you recover pp and qq with the quadratic formula. Finding φ(n)\varphi(n) is equivalent to factoring.

Take the ee-th root of cc directly. Over the integers, ee-th roots are easy. Modulo a composite nn whose factorization you do not know, the "RSA problem" of computing c1/e mod nc^{1/e} \bmod n has no known efficient solution. Worth being precise, though: it is not proven that breaking RSA is as hard as factoring. Factoring breaks RSA; whether every attack on RSA implies a factoring algorithm is an open question.

Brute-force mm. Encrypt candidate messages and compare — devastating whenever the message space is small, and the reason padding exists.

Practical consequences: 1024-bit keys are deprecated, 2048 bits is the current floor, and 3072 bits corresponds to roughly the 128-bit security level in NIST's guidance. And the whole edifice has an expiry date: Shor's algorithm factors integers in polynomial time on a sufficiently large quantum computer, which is why standards bodies are migrating to post-quantum schemes. Nothing about the classical analysis above protects data that an adversary can record now and decrypt later.

Textbook RSA is not RSA you can ship

Everything up to here is what cryptographers call textbook RSA, and deploying it as written is a serious vulnerability. The mathematics is correct; the scheme built directly on it is broken. Three reasons, each fatal on its own.

It is deterministic. The same plaintext always produces the same ciphertext under the same key. So an attacker who can guess the message space wins without any number theory: encrypt every candidate with the public key — which is public — and match. A yes/no field, a credit card number, a room number, an AES key from a weak generator: all recoverable by table lookup. Formally, textbook RSA fails IND-CPA security, and the fix must involve randomness.

It is malleable. Because exponentiation distributes over multiplication,

c1c2≡m1em2e=(m1m2)e(modn)c_1 c_2 \equiv m_1^{e} m_2^{e} = (m_1 m_2)^{e} \pmod{n}

In words: multiply two ciphertexts and you get the ciphertext of the product of their plaintexts. So an attacker who cannot read a ciphertext can still multiply it by 2e2^e and hand you something that decrypts to 2m2m. Any protocol that treats "it decrypted" as "it is authentic" is exploitable.

Small exponents leak small messages. If me<nm^e < n — easy to arrange with e=3e = 3 and a short message — then c=mec = m^e holds over the integers, with no wrap-around at all. Take the ordinary integer cube root of cc and read the message. No factoring required.

The cure is padding: a standardized transformation that pads the message out to nearly the full width of nn, mixes in fresh random bytes, and adds structure the decryptor can verify.

There is a further reason RSA never encrypts your actual data: m<nm < n caps a 2048-bit key at 256 bytes minus padding overhead, and modular exponentiation is orders of magnitude slower than a symmetric cipher. Real systems are hybrid — RSA (when used at all) encrypts a short symmetric key, and AES encrypts the payload. In modern TLS 1.3 even that role is gone: key establishment moved to ephemeral Diffie–Hellman for forward secrecy, and RSA survives mainly as a signature algorithm on certificates. (See TLS from Scratch for how those pieces fit.)

Signatures: the same two exponents, swapped

Because ee and dd are inverses, applying them in the other order also round-trips: (md)e≡m(m^d)^e \equiv m. That gives a digital signature. The signer, who alone holds dd, produces

s≡H(m)d(modn)s \equiv H(m)^{d} \pmod{n}

and anyone verifies by checking

se≡?H(m)(modn)s^{e} \stackrel{?}{\equiv} H(m) \pmod{n}

using the public key. Only the holder of dd could have produced an ss that passes, so the signature binds the message to that key. Read together, the two equations say, in words: the signer locks the hash with the private exponent, and the verifier unlocks it with the public exponent and checks it against a hash computed locally.

Note the hash HH. You sign a digest, not the message, for two reasons: the message is almost always longer than nn allows, and signing raw values inherits the malleability above — an attacker can multiply two signatures together to forge a signature on the product. A collision-resistant hash plus PSS padding closes both. This is the operation that actually matters for the modern web: every certificate chain your browser validates is a stack of these verifications.

Pitfalls

Never implement it yourself. This is the standard advice and it is correct. Everything below is a real class of break in deployed RSA, and library authors have spent decades on each.

Standards use λ(n)\lambda(n), not φ(n)\varphi(n). FIPS and PKCS#1 define dd modulo λ(n)=lcm⁡(p−1,q−1)\lambda(n) = \operatorname{lcm}(p-1, q-1), which divides φ(n)\varphi(n). The proof still works and the resulting dd is smaller, hence faster. If you compare a library's dd against one you derived from φ(n)\varphi(n) they may differ while both being valid — the exponents are congruent mod λ(n)\lambda(n).

Weak randomness is catastrophic, and it composes across keys. If a bad entropy source makes two independently generated keys share a prime, then gcd⁡(n1,n2)\gcd(n_1, n_2) reveals it, and both keys fall — to a computation costing nothing. A 2012 survey ("Mining your Ps and Qs") found tens of thousands of live TLS hosts broken exactly this way, mostly embedded devices generating keys at first boot before any entropy existed.

Timing is a side channel. Naive modular exponentiation takes a different amount of time depending on the bits of dd, and that has been used to extract private keys over a network. Real implementations use blinding — multiply by a random factor before exponentiating and divide it out after — plus constant-time code paths.

Fault attacks on the CRT speedup. Decryption is usually done via the Chinese Remainder Theorem, computing mod pp and mod qq separately for about a 4× speedup. If a single computation error corrupts one of the two halves, comparing the faulty signature with a correct one reveals pp by a GCD. Libraries defend by verifying the result before releasing it.

Small private exponents are breakable. Choosing a tiny dd to speed up decryption invites Wiener's attack, which recovers dd efficiently when dd is roughly below n1/4n^{1/4}. Small public exponents are fine — e=65537e = 65537 is standard — but dd must be large.

Padding must be checked in constant time, and errors must not be distinguishable. This is the Bleichenbacher lesson: the vulnerability was never in the arithmetic, it was in the error message.

How it is used in practice

Almost nobody writes RSA arithmetic on the job. What you actually touch is key generation, key formats, and the choice of padding — and that is where the accidents happen.

When it comes up. Backend and infrastructure engineers meet RSA when generating a CSR for a TLS certificate, issuing SSH keys, or choosing a JWT signing algorithm. Security reviewers meet it when inventorying key sizes and padding modes across existing systems.

The commands you type and the names they print.

Mistakes that bite people who don't know.

Questions you get asked in interviews and design reviews, and how to answer.

Summary

For the surrounding landscape, start with Cryptography from Scratch; to see RSA signatures doing their job inside a real handshake, continue to TLS from Scratch.

Check your understanding

  1. Key generation requires gcd⁡(e,φ(n))=1\gcd(e, \varphi(n)) = 1. What exactly breaks if you pick an ee that violates it?
  2. Why is dd computed modulo φ(n)\varphi(n) rather than modulo nn?
  3. Using the public key (33,3)(33, 3) from the worked example, encrypt m=2m = 2. Then use d=7d = 7 to decrypt your answer and confirm you get 2 back.
  4. An attacker learns φ(n)\varphi(n) for some 2048-bit RSA key but not pp or qq. Is the key compromised? Justify your answer.
  5. A service encrypts a single-digit PIN with textbook RSA using a 2048-bit public key and stores the ciphertext. The key is strong and factoring is infeasible. Explain how an attacker reads the PIN anyway, and which countermeasure prevents it.
Answers
  1. If ee shares a factor with φ(n)\varphi(n), then ee has no modular inverse mod φ(n)\varphi(n), so no dd with ed≡1(modφ(n))ed \equiv 1 \pmod{\varphi(n)} exists. Step 5 has no solution and there is no private key — the coprimality condition is exactly the existence condition for the inverse.
  2. Because Euler's theorem makes mφ(n)≡1(modn)m^{\varphi(n)} \equiv 1 \pmod n, so multiplying the exponent by another φ(n)\varphi(n) changes nothing: exponents wrap around mod φ(n)\varphi(n), not mod nn. Decryption needs eded to be one more than a multiple of φ(n)\varphi(n), which is the definition of dd being e−1e^{-1} in that modulus.
  3. c≡23=8(mod33)c \equiv 2^3 = 8 \pmod{33}, so c=8c = 8. Decrypting: 82=64≡64−33=318^2 = 64 \equiv 64 - 33 = 31; 84≡312=961=29×33+4≡48^4 \equiv 31^2 = 961 = 29 \times 33 + 4 \equiv 4; 86=84⋅82≡4×31=124=3×33+25≡258^6 = 8^4 \cdot 8^2 \equiv 4 \times 31 = 124 = 3 \times 33 + 25 \equiv 25; 87≡25×8=200=6×33+2≡28^7 \equiv 25 \times 8 = 200 = 6 \times 33 + 2 \equiv 2. The message 2 comes back.
  4. Yes, completely. From n=pqn = pq and φ(n)=n−p−q+1\varphi(n) = n - p - q + 1 you get p+q=n−φ(n)+1p + q = n - \varphi(n) + 1; knowing the sum and product of two numbers, the quadratic formula gives pp and qq. Computing φ(n)\varphi(n) is equivalent to factoring nn, and either one yields dd immediately.
  5. Textbook RSA is deterministic and the public key is public, so the attacker encrypts each of the ten possible PINs and compares with the stored ciphertext — ten exponentiations, no factoring. The countermeasure is randomized padding (RSAES-OAEP), which mixes fresh random bytes into every encryption so the same PIN produces a different ciphertext each time and the lookup table cannot be built.
🎬 Watch this explained (Manim, captioned)

References

  1. "Rivest. "Rivest
  2. A Method for Obtaining Digital Signatures and Public-Key Cryptosystems. Shamir & Adleman 1978
  3. https://dl.acm.org/doi/10.1145/359340.359342". CACM 21(2):120-126
  4. PKCS #1: RSA Cryptography Specifications Version 2.2. RFC 8017Paper page

This article is written from the source paper above. Where they differ, the original is authoritative.

Published by TechAthletes · Editorial policy · How we cite · Corrections

Comments

Sign in to comment