A Brief Introduction to Modern Cryptography
This semester, I took a class called Discrete Mathematics for Computer Science. We learned a variety of important concepts for mathematics and comp sci, including set theory, propositional logic, proofs, and trees. One interesting topic we covered was the RSA algorithm, the basis for modern cryptography. One part of our final project involved writing code to implement the RSA algorithm. In this blog post, I’ll attempt to explain how the RSA algorithm works, and how it keeps all of your passwords and credit card numbers and personal information safe.
Before jumping into the algorithm, it’s worth discussing why encryption is important. When you try to load a webpage, you send the URL you want to load off to the router, and after a surprisingly large number of hoops, you get back a response that your browser can display to you. When you log into a website, or enter your credit card information, that data has to be sent to the server to be processed. Without encryption, anyone on the same WiFi network as you could look at the data you’re exchanging with all of the websites you’re visiting. When you go to log in, a malicious actor could see you send your username and password, and then they can access your account with little effort. When your internet traffic is encrypted, the hacker could still know which websites you’re visiting, but the data you send would look like gibberish, and thus, the hacker couldn’t steal your information. The challenge is to create a system where your computer and the server you’re accessing can exchange information privately without a third party being able to know what information is being sent. Some sort of initial information has to be conveyed to establish the cryptosystem (the cryptographic function and any associated values), and this information cannot be encrypted. This is where prime numbers come in.
It may seem rather surprising, but the whole of modern cryptography is based on the assumption that it’s hard to factor numbers into a collection of prime numbers (ex. 12 = 2 * 2 * 3). If some could devise an algorithm or device that could easily perform prime factorizations, all of modern encryption would be broken in an instant. However, decades of research and proofs have shown that prime factorizations of large numbers take millions of years of computer time to compute, and so we are safe for now. Let’s look at how prime numbers are used by the RSA algorithm to encrypt and decrypt messages.
First, we will need two prime numbers, which we will call p and q. In everyday situations, p and q would be hundreds of digits long, but for the purposes of this explanation, we will choose small values for p and q, namely 43 and 59 respectively. Once we have these values, we compute n = p * q and k = (p – 1) * (q – 1). In this case, we get n = 2537 and k = 2436. As we will see soon, if a hacker could reverse the value of n to get the individual terms p and q, they could encrypt and decrypt any message in this cryptosystem. It is clear, then, that our choices of p and q are extremely insecure, because it would not take long for a computer to discover them from the given value of n (because n is so small). Although it may seem counterintuitive, when p and q are very large, it becomes infeasible to discover them them from their product n.
Next, we must computer two special numbers, the encryption number e and the decryption number d. To find e, we must find a number such that the greatest common divisor of e and k (which is (p – 1) * (q – 1)) is 1. In practical terms, e is a number that has no factors in common with k. To do this computation, we can use the Euclidean algorithm, an algorithm that efficiently computes the greatest common divisor of two numbers. To find values of e, we consider every number starting at 2 and compute the gcd of e and k. For many values, the gcd will be 0, meaning e is a factor of k. For example, if we compute gcd(2, 2436), we find that 2436 = 2 * 1218 + 0, and thus the remainder is 0. For some special values, we find the gcd to be 1. For example, if we compute gcd(5, 2436), we find that 2436 = 5 * 487 + 1, and thus there is a remainder of 1. This tells us that 5 is not a factor of 2436 (as is probably obvious from inspection), and thus it is a valid choice for e. For the purposes of this demonstration, we will choose e = 13, noting that the following chain of operations shows that gcd(13, 2436) = 1:
2436 = 13 * 187 + 5
13 = 5 * 2 + 3
5 = 3 * 1 + 2
3 = 2 * 1 + 1
Now that we have our encryption number e, we must find out decryption number d. This will be the inverse of e modulo k; that is, the number that, when multiplied by e, gives a remainder of 1 when divided by k. To find this number, we can use the extended Euclidean algorithm, which essentially works backwards from the Euclidean algorithm to find two coefficients such that, when the original numbers are multiplied by those coefficients and the results are added together, the sum is the gcd. I know this is probably confusing, but consider the following string of operations that essentially work backwards from the set of operations above:
1 = 1 * 3 + -1 * 2
1 = -1 * 5 + 2 * 3
1 = 2 * 13 + -5 * 5
1 = -5 * 2436 + 937 * 13
We have found our two coefficients, -5 and 937. We find that if we multiply 2436 (one of our original numbers) by -5, and 13 (the other original number) by 937, we get 1 (the gcd we found previously). Thus, we know that our decryption number is the coefficient for the smaller number, 937 in this case. Although this may be confusing, the math isn’t as important as the basic understanding that we now have a few key numbers that we can use for our encryption and decryption. If you are in my CAS class, this is what I was doing on the board on Wednesday morning.
Now that we have p, q, n, k, e, and d, we can construct two keys: a public key that we send to the client (n, e), and a private key that we keep on the server (n, d). The client, who knows n and e, and encrypt any message. The server, which knows n and d, can decrypt any message. But the client knowing e is not enough for it to find d, because finding d required knowing k, and to know k you must know p and q, and as we stated, it is computationally infeasible to extract p and q from n when n is sufficiently large (hundreds of digits). This is the key to RSA cryptography, and if nothing else makes sense, hopefully this will. The server transmits just enough information for the client to encrypt any message, but to figure out the decryption key, it would take millions of years of computer time.
Now, let’s encrypt the message STOP. To do so, we must first convert each letter to a number representation. We will use the numbers 00 through 25 to represent each letter, where 00 = A and 25 = Z. In this case, STOP becomes 18191415. The next step is to split the number up into a series of blocks of a certain length. The formula for calculating the length is unimportant, so it will suffice to say that the block length here is 4. Thus, we have two blocks: 1819 and 1415. We encrypt each block separately using the encryption function c = m^e (mod k) where c is the encrypted message and m is the numerical form we found. We can use an algorithm called fast modular exponentiation to quickly compute this value for large values, but in this case we will find that 1819 becomes 2081 and 1415 becomes 2182. Thus, our encrypted message is 20812182. We can send this message through potentially unsafe territory, because if we trust the basis of RSA cryptography, a hacker would have no hope of decrypting that message given just the values of the public key (n, e).
Now, let’s decrypt a message on the server side. Suppose the server has received the message 09810461. The first step is to split the input up into blocks. Again, we will find that the block length is 4, so we have two blocks: 0981 and 0461. For each block, we use the decryption function m = c^d (mod k) where c is the encrypted message and m is the unencrypted form. Again, we can use fast modular exponentiation to find that 0981 becomes 0704 and 0461 becomes 1115. Finally, we just convert each chunk of two numbers to its corresponding letter. We find that 07 becomes H, 04 becomes E, 11 becomes L, and 15 becomes P. Thus, the encrypted message turned out to be HELP.
This was just a brief (although not as brief as I wanted it to be) introduction to the underpinnings of modern cryptography. Although it can be confusing at first, it really just boils down to this: if you can figure out how to factor a huge number into a series of primes, you can destroy modern cryptography as we know it and plunge the world into chaos. Until then, we will trust in our prime numbers to keep our precious data safe.