Reading: MCS 9.11
Example
The goal of public key cryptography is to transmit secrets over a completely public channel with no coordination before hand.
We assume there is a sender who has a message that they want to transmit to a recipient, with no other party able to observe the contents of the message. To accomplish this, the recipient generates a public/private key pair. They broadcast the public key to everybody, and keep the private key to themselves.
The goal of the cryptosystem is that anybody with the public key can use it to encrypt a message so that only somebody with the private key can decrypt it.
RSA is a particular public key cryptography scheme. The idea is simple: the public key consists of a large modulus \(m \in \mathbb{Z}\) and a unit \([k] \in \mathbb{Z}_{φ(m)}\).
The private key is the inverse of \([k]\) mod \(φ(m)\).
To encrypt a message \(msg\) (sometimes referred to as the plaintext), the sender first breaks the message into pieces, each of which is a number that is smaller than \(m\); from now on, we will assume \(0 \leq msg \lt m\). This way, if the recipient computes the unique representative of \([msg]_m\) that is between \(0\) and \(m\), they will have the message.
The encrypted message (often referred to as the cyphertext) is simply \(c = [msg]^{[k]}\).
To decrypt, the recipient computes \(c^{[k]^{-1}}\). By definition, this is \(([msg]^{[k]})^{[k^{-1}]} = [msg]^{[k][k]^{-1}}\). By Euler's theorem, we can do arithmetic in the exponent as long as we consider the elements as equivalence classes mod \(φ(m)\); this tells us that \([msg]^{[k][k]^{-1}} = [msg]^{[1]} = [msg]\).
At first glance, this scheme doesn't seem secure. An attacker knows \(m\) and \(k\), so they can compute \(φ(m)\) (for example by factoring \(m\), or even enumerating the elements of \(\mathbb{Z}_m\) and checking whether each is a unit). They can then use the Bezout coefficients to compute \(k^{-1}\), and can then do anything the recipient can.
The security of RSA depends on the assumption that these operations take exponential time in the size of the keys, but that the operations that the "good guys" (the sender and recipient) need to perform can be computed in polynomial time.
This means that no matter how fast the attacker's computer is, the recipient can always add a few digits to the key to increase the attacker's computational work by a factor of \(10^{\text{a few}}\); if the attacker takes 5 minutes to compute \([k]^{-1}\), the recipient can easily add a few digits to raise this to years, or even decades or millenia. Of course, it will also increase the time it takes the sender and receiver to encrypt and decrypt, but it will not raise them exponentially; a few extra digits will only add a small amount of extra work.
Here we list the protocol in some detail. Since the security depends on the efficiency of the operations performed by the good guys, we sketch algorithms for each of the necessary operations.
How is this done? The exact procedure is well beyond the scope of this course, but here's the basic idea. It turns out that "many" large numbers are prime. To choose a large prime, choose a large odd number (odd because you know if it's even it's not prime). Check to see if it is prime. If not, add two and try again. Because large primes are common, the expected number of number needed to check is small.
How to check for primality? There are randomized algorithms one can perform that, if a number is prime, say it is prime, and if not, say it is not, but are incorrect with some probability \(p \lt 1\). Moreover, separate runs of the algorithm fail independently, so by running this algorithm twice, one can reduce the probability of error to \(p^2\). Running it 100 times reduces the probability to \(p^{100}\). In fact, the probability can be made arbitrarily small.
It is likely that there is a small prime that is not a factor of \((p-1)(q-1)\) (and thus is a unit). People typically start with a small prime like \(7\), check to see if it is a unit, and if not, move on to the next prime. The expected number of primes to check is small.
To check whether \([k]\) is a unit, we can use the fact that \([k]_{φ(m)}\) is a unit if and only if \(gcd(k,φ(m)) = 1\). Euclid's algorithm efficiently computes the gcd of \(k\) and \(φ(m)\).
Here is a clever trick for computing \([a^k]\) for large \(k\). First, write \(k\) in binary: \(k = d_02^0 + d_12^1 + d_22^2 + \cdots + d_{j-1}2^{j-1} + d_j2^j\). Then \[[a^k] = [a^0]^{d_0}[a^2]^{d_1}[a^4]^{d_2}[a^8]^{d_3}\cdots[a^{2^j}]^{d_j}\] We can find \([a^{2^i}]\) for all the \(i\) by repeatedly squaring \([a]\). Then we just multiply together the ones that have \(d_i = 1\). This requires at most one multiplication per digit of \(k\), and is thus efficient.
For example, to compute \([3^{37}]_{7}\), we note that in binary, \(37 = 32 + 4 + 1 = (100101)_2\). Then we need to compute and multiply \([3^1]\), \([3^4]\), and \([3^{32}]\). We just start squaring; since we're only interested in equivalence classes mod 7, we can reduce mod 7 at each step to keep our numbers small: - \([3^1] = [3]\). - \([3^2] = [9] = [2]\). - \([3^4] = [(3^2)^2] = [2^2] = [4]\). - \([3^8] = [(3^4)^2] = [4^2] = [16] = [2]\). - \([3^{16}] = [(3^8)^2] = [2^2] = [4]\). - \([3^{32}] = [(3^{16})^2] = [4^2] = [16] = [2]\). - \([3^{37}] = [3^1][3^4][3^32] = [3][4][2] = [24] = [3]\).
We could have done this more simply by reducing \(37\) mod \(φ(7)\), but that requires us to know \(φ(7)\), which we'll argue below is hard.
The sender broadcasts \(c = [msg]^k_m\)
The recipient raises \(c^{[k]^{-1}}\)
The attacker sees \(c\), \(m\), and \(k\). We lose the game if they can (efficiently) compute \(msg\).
If they can compute any of the intermediate parts of the computation that the reciever uses, they can simulate the receiver. For example, if they know \(p\), they can divide \(m\) by \(p\) to find \(q\), and can then compute \(φ(m)\), and then \([k]^{-1}\), just like the recipient. Even without \(p\) and \(q\), if they know \(φ(m)\), they can still compute \(k^{-1}\).
The best known algorithms for factoring take exponential time. It is widely believed that any algorithm to factor a number will take exponential time, but nobody has yet been able to prove this.
Note that it is easy to write an efficient non-deterministic algorithm for decryption: Start by guessing \(p\) and \(q\), then check that \(m = pq\). Not only is it not known whether factoring can be done efficiently, it is not even known whether there are any problems that can be solved efficiently non-deterministically but can't be solved efficiently deterministically.
This problem is stated concisely by asking whether "\(P = NP\)"; \(P\) is the class of problems that can be solved deterministically in polynomial time, while \(NP\) is the class of problems that can be solved non-deterministically in polynomial time.
\(P = NP\) is literally one of the "million dollar questions": the Clay Institute has offered a one million dollar prize if you can prove or disprove it. However, if you are able to prove or disprove it, you can probably do much better than a million dollars, either by breaking into everybody's bank account or by developing and selling technology based on your proof.