This article has a more math-focused version with formal proofs on Folio.
In the previous three parts, we built RSA from the ground up: modular arithmetic, Euler's theorem, and a working Python implementation. Now we tear it all down. This final article explores why RSA is showing its age, how elliptic curve cryptography (ECC) offers a compelling alternative, and what the quantum future holds.
Part I: Attacks on RSA
The Factoring Arms Race
RSA's security rests on a single assumption: factoring the product of two large primes is computationally hard. But "hard" is a moving target.
Trial Division — The naive approach tests every integer up to . For a 2048-bit modulus, that means checking roughly candidates. Completely infeasible.
Fermat's Factorization — If and are close together, we can write and search for near . This is why good RSA implementations ensure is large.
Quadratic Sieve (QS) — Introduced in 1981, QS was the fastest general-purpose factoring algorithm for decades. Its sub-exponential runtime is approximately:
General Number Field Sieve (GNFS) — The current champion, with heuristic complexity:
In 2020, a team factored RSA-250 (829 bits) using GNFS. The trend is clear: factoring records keep falling.
| Year | Record | Bits |
|---|---|---|
| 1999 | RSA-155 | 512 |
| 2005 | RSA-200 | 663 |
| 2009 | RSA-768 | 768 |
| 2020 | RSA-250 | 829 |
Side-Channel Attacks
Even with a large key, the implementation can leak secrets:
-
Timing attacks: Measuring how long decryption takes can reveal bits of the private key. If a
1bit in the exponent triggers an extra multiplication, the time difference is measurable. - Power analysis: Monitoring a hardware device's power consumption during RSA operations can expose the exponent bit by bit.
- Fault injection: Inducing a computational error during signing and comparing the faulty signature with a correct one can factor directly (Boneh-DeMillo-Lipton attack).
Padding Oracle Attacks
Raw textbook RSA ( ) is deterministic — the same plaintext always produces the same ciphertext. This enables:
- Bleichenbacher's attack (1998): Against PKCS#1 v1.5 padding. By sending thousands of crafted ciphertexts and observing which ones the server accepts as "correctly padded," an attacker can decrypt any message. This motivated the move to OAEP padding.
- Chosen-ciphertext malleability: Given , an attacker computes , which decrypts to .
Part II: The Key Length Problem
As factoring algorithms improve, RSA keys must grow. Here is what NIST recommends for equivalent security levels:
| Security (bits) | RSA key size | ECC key size | Ratio |
|---|---|---|---|
| 80 | 1024 | 160 | 6.4x |
| 112 | 2048 | 224 | 9.1x |
| 128 | 3072 | 256 | 12x |
| 192 | 7680 | 384 | 20x |
| 256 | 15360 | 512 | 30x |
At the 256-bit security level, RSA needs a 15,360-bit key. That is 1,920 bytes just for the modulus. Key generation, signing, and decryption all slow to a crawl. This is the fundamental scaling problem that drives the shift to elliptic curves.
Part III: Introduction to Elliptic Curves
An elliptic curve over a field (where is a large prime) is the set of points satisfying:
together with a special "point at infinity" , provided the discriminant (which ensures no singularities).
Group Structure
The remarkable fact: the points on an elliptic curve form an abelian group under a geometric addition law.
- Identity: The point at infinity serves as the identity element.
- Inverse: The inverse of is .
- Addition: For two distinct points and , the sum is computed by drawing a line through and , finding the third intersection with the curve, and reflecting over the x-axis.
The formulas (mod ):
For point doubling ( ):
Part IV: Python Implementation
Let's implement elliptic curve arithmetic from scratch.
class EllipticCurve:
"""An elliptic curve y^2 = x^3 + ax + b over F_p."""
def __init__(self, a, b, p):
self.a = a
self.b = b
self.p = p
assert (4 * a**3 + 27 * b**2) % p != 0, "Singular curve"
def is_on_curve(self, point):
if point is None: # Point at infinity
return True
x, y = point
return (y * y - (x**3 + self.a * x + self.b)) % self.p == 0
def point_add(self, P, Q):
"""Add two points on the curve."""
if P is None:
return Q
if Q is None:
return P
x1, y1 = P
x2, y2 = Q
if x1 == x2 and (y1 + y2) % self.p == 0:
return None # P + (-P) = O
if P == Q:
# Point doubling
lam = (3 * x1 * x1 + self.a) * pow(2 * y1, -1, self.p) % self.p
else:
# Point addition
lam = (y2 - y1) * pow(x2 - x1, -1, self.p) % self.p
x3 = (lam * lam - x1 - x2) % self.p
y3 = (lam * (x1 - x3) - y1) % self.p
return (x3, y3)
def scalar_mult(self, k, P):
"""Compute k * P using double-and-add."""
result = None # Point at infinity
addend = P
while k:
if k & 1:
result = self.point_add(result, addend)
addend = self.point_add(addend, addend)
k >>= 1
return result
The scalar_mult method uses the double-and-add algorithm — the elliptic curve analog of fast modular exponentiation. Computing
takes
group operations.
A Quick Test
# secp256k1 parameters (used in Bitcoin)
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
a = 0
b = 7
G = (
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
)
curve = EllipticCurve(a, b, p)
assert curve.is_on_curve(G)
# Compute 2G
G2 = curve.scalar_mult(2, G)
print(f"2G = ({hex(G2[0])}, {hex(G2[1])})")
assert curve.is_on_curve(G2)
Part V: ECDH Key Exchange
Elliptic Curve Diffie-Hellman lets two parties agree on a shared secret over an insecure channel. It is the ECC counterpart to classical Diffie-Hellman.
import secrets
def ecdh_keygen(curve, G, n):
"""Generate an ECDH key pair."""
private_key = secrets.randbelow(n - 1) + 1 # random in [1, n-1]
public_key = curve.scalar_mult(private_key, G)
return private_key, public_key
# Curve order for secp256k1
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# Alice generates her key pair
alice_priv, alice_pub = ecdh_keygen(curve, G, n)
# Bob generates his key pair
bob_priv, bob_pub = ecdh_keygen(curve, G, n)
# Both compute the shared secret
shared_alice = curve.scalar_mult(alice_priv, bob_pub) # alice_priv * bob_pub
shared_bob = curve.scalar_mult(bob_priv, alice_pub) # bob_priv * alice_pub
assert shared_alice == shared_bob
print("Shared secret established!")
print(f" x = {hex(shared_alice[0])}")
Alice computes . Bob computes . They arrive at the same point. An eavesdropper sees and but cannot compute without solving the Elliptic Curve Discrete Logarithm Problem (ECDLP).
Part VI: Why ECDLP Is Harder Than Factoring
The best known classical algorithms for the two problems scale very differently:
| Integer Factoring (RSA) | ECDLP (ECC) | |
|---|---|---|
| Best algorithm | GNFS | Pollard's rho |
| Complexity class | Sub-exponential | Fully exponential |
| Time ( -bit key) | ||
| 128-bit security | 3072-bit key | 256-bit key |
| Index calculus | Applies | No known analog |
The critical difference is that index calculus methods — the family of techniques behind QS and GNFS — have no known efficient analog for elliptic curves over prime fields. Transferring the discrete logarithm from an elliptic curve to a finite field where index calculus works requires solving the ECDLP itself (outside of special weak-curve cases).
This means that for elliptic curves, we are stuck with generic algorithms like Pollard's rho, which run in where is the group order. A 256-bit curve provides roughly 128 bits of security — matching AES-128 — with dramatically smaller keys.
Part VII: The Quantum Threat
Shor's Algorithm
In 1994, Peter Shor showed that a sufficiently large quantum computer can factor integers and compute discrete logarithms in polynomial time:
This breaks both RSA and ECC. The algorithm works by reducing factoring to period-finding, which quantum computers solve efficiently via the Quantum Fourier Transform. For ECC, a variant of Shor's algorithm solves the ECDLP with similar efficiency.
Current status: As of 2025, the largest number factored by a quantum computer using Shor's algorithm is 21. We are far from threatening RSA-2048. But the field is advancing rapidly, and the "harvest now, decrypt later" threat is real — adversaries can store encrypted traffic today and decrypt it once quantum computers mature.
Post-Quantum Cryptography
NIST finalized its first post-quantum standards in 2024:
- ML-KEM (CRYSTALS-Kyber): A lattice-based key encapsulation mechanism. Its security rests on the hardness of the Module Learning With Errors (MLWE) problem.
- ML-DSA (CRYSTALS-Dilithium): A lattice-based digital signature scheme.
- SLH-DSA (SPHINCS+): A hash-based signature scheme — conservative, stateless, and relying only on hash function security.
The core idea behind lattice-based cryptography: given a "noisy" system of linear equations over a lattice, finding the secret vector is believed hard even for quantum computers.
where is a small error vector. Without the error, this is simple linear algebra. With it, the problem becomes intractable.
Part VIII: RSA vs ECC — Full Comparison
| Feature | RSA | ECC |
|---|---|---|
| Hard problem | Integer factoring | ECDLP |
| Key size (128-bit security) | 3072 bits | 256 bits |
| Signature size | 3072 bits | 512 bits |
| Key generation speed | Slow (primality testing) | Fast |
| Signing speed | Moderate | Fast |
| Verification speed | Fast (small ) | Moderate |
| Bandwidth efficiency | Poor | Excellent |
| Quantum resistance | None | None |
| Standardization maturity | Decades | ~15 years in TLS |
| Patent concerns | Expired | Mostly resolved |
| IoT / embedded suitability | Poor | Excellent |
The bottom line: for new systems, ECC is the default choice. RSA remains widespread in legacy systems and in contexts where its simplicity and fast verification are valued (e.g., certificate chains). Neither survives a quantum computer, so the long-term future belongs to post-quantum schemes.
Folio: The Full Mathematical Treatment
I wrote this series on Dev.to for the code-focused approach, but for the rigorous math — formal proofs, theorem environments, cross-references — I've published a more detailed version on Folio's number theory series. There you'll find complete proofs of Euler's theorem, the Chinese Remainder Theorem, formal security reductions, and the algebraic geometry behind elliptic curves — all typeset with proper LaTeX.
Series Summary
Over four articles, we've traced the full arc of public-key cryptography:
#1 — Modular Arithmetic: The foundation. We built intuition for congruences, modular inverses, and the structure of .
#2 — Euler's Theorem and Key Generation: We proved why RSA works — that — and implemented key generation in Python.
#3 — Encryption, Signatures, and Attacks: We built a complete RSA system with OAEP padding, digital signatures, and explored common implementation pitfalls.
#4 — Breaking RSA and Elliptic Curves (this article): We examined RSA's weaknesses, implemented elliptic curve cryptography from scratch, and looked ahead to the post-quantum era.
The story of RSA is ultimately a story about the relationship between mathematics and computation. What is "hard" today may not be tomorrow. The algorithms we trust are only as strong as the problems they hide behind — and finding those problems, understanding their difficulty, and building systems around them is one of the most fascinating intersections of pure math and engineering.
Thanks for reading the series. If you have questions or spot errors, drop a comment below.
Enjoyed this series? Follow me for more articles on the mathematics of computer science. The full version with formal proofs is available on Folio.
Top comments (0)