RSA: The unbreakable code?
Overview
Overview
Keywords: encryption, decryption, modulo arithmetic, plaintext, ciphertext, modular exponentiation, cryptography
Age group: 16-18 years
Required knowledge/skills: basic understanding of Python
Time frame: 120 min (two lessons)
Author: Sam Robbins (UK)
Lesson plan
| Timing in minutes | Lesson phases | Activity | Resources |
|---|---|---|---|
| 0–15 | Exercise 1 | Introduction to Fermat’s Little Theorem + exercise to be done on pen and paper using a calculator. | Worksheet, calculator |
| 15–35 | Exercise 2 | Teacher explains how to implement mod-ular exponentiation through repeated multiplication within modular arithmetic. Students implement the code listing shown and test with the numbers given in the exercise. | Worksheet, Python interpreter (an online editor should be sufficient) |
| 35–45 | Exposition of the RSA system and an example | Teacher explains how encryption and de-cryption work in RSA with reference to an example. | Worksheet, Python interpreter (an online editor should be sufficient) |
| 45–75 | Exercise 3 | Students adapt code to encrypt and then decrypt a message using RSA. | Worksheet, Python interpreter (an online editor should be sufficient) |
| 75–120 | Exercise 4 | Students write code to factorise the number given and time how long it takes. | Worksheet, Python interpreter (online editor may not be be sufficient for this exercise) |
Student exercises and teacher notes
In this lesson, we will focus on the RSA encryption algorithm and classical methods to break it. The RSA algorithm was designed in 1977 by Rivest, Shamir and Adleman and remains one of the most successful forms of encryption to this day. If you have ever performed a financial transaction over the internet, it is likely that the security codes you used to do that will have been encrypted, and your identity verified, using the RSA system.
This lesson will look at the RSA methodology and show how, in the classical world, it is practically unbreakable.
Worksheet
The following tasks are available as worksheet for download as pdf and docx.
The tasks are also available as an interactive worksheet with embedded Python environment here.
Fermat’s Little Theorem
At the heart of the RSA system is a beautiful mathematical result known as Fermat’s Little Theorem:
This states that any positive integer, , when raised to the power of a prime number, , minus one will give a remainder of one when the result is divided by . The mod function here stands for modulo, which means “the remainder after division by” (in this case, ).
For example, take and :
Exercise 1
Check Fermat’s Little Theorem holds for the following values of and :
Teacher should first model how modulo arithmetic works by showing the remainder of various numbers after division by another number. The answer to every part of this question is 1.
Programming issue
The final example in Exercise 1 hints at a potential problem in implementing the RSA system, namely that the power function grows very quickly. Even with very small prime numbers, we will quickly get overflow errors if we simply calculate the power function in full. However, we can avoid this problem by using the fact that raising a number to a power in modulo arithmetic is the same as raising the remainder to the same power.
For example, taking and again, we can see that:
But
So rather than calculating under the modulo operator, we can just calculate and easily see that
as expected.
We can therefore write a Python routine to calculate the power function in modulo arithmetic (also known as modular exponentiation), using a loop to multiply by itself repeatedly but each time replacing the result with its remainder under division by .
Exercise 2
Implement the following Python code (note that the % symbol implements the modulo function in Python):
def powerMod(a, b, c): # raises a to the power of b modulo c res = a % c for i in range(0, b - 1): res = a * res % c return res x = 5 p = 11 print( powerMod(x, p - 1, p) )
Use the code to deduce which of the following are not prime numbers (because Fermat’s Little Theorem does not hold for them):
- 1367
- 1369
- 7527
- 7531
- 7537
- 15723
- 15727
- 18791
Teacher models how exponentiation can be performed by successive multiplication of a number by itself, each time replacing the result by its modulo equivalent (an example is provided on the worksheet that may be used).
Students then implement the code as written to test the numbers provided. The % symbol is the Python command for MOD. Students can use any value of x less than p for each of the questions. Using x=2 would be good enough for all questions. Any value of p for which the number returned by the function is not 1 is not a prime.
Answers: (b), (c), (d), (f) and (h) are not prime.
The RSA algorithm
Note
The next section goes through the mathematics of the RSA system, step by step. This section may be skipped, if the teacher prefers to focus on how the system works rather than why. The summary table at the bottom of this section provides all the information needed to implement the system.
RSA relies on a variant of Fermat’s Little Theorem that says the following:
where and are both primes.
For example, if , and :
Multiplying both sides of the identity by gives:
This means that raised to the power of will return .
The RSA algorithm uses this fact to create a two-step process for first encrypting and then decrypting a message. If we can find two numbers and such that
then
.
We now define to be our encryption key and to be our decryption key and to be the message we wish to encrypt.
Step 1 – encryption
We raise to the power to generate an encrypted message .
The strength of the RSA system is built on the fact that knowledge of , and is not sufficient to retrieve . There is no way to find the -th root in modulo arithmetic.
This can easily be seen from Fermat’s Little Theorem itself:
So if we attempted to take the fourth root of we would not be able to tell whether the original number was , , , or .
Step 2 – decryption
Once we have encrypted our message as , the only way to decrypt the message is by raising to the power of . Using power rules, we can see that:
The RSA algorithm: summary of process
| Sender | General public | Receiver |
|---|---|---|
| What they know | ||
| x (message to be encrypted) e (the encryption key) pq (the modulus to be used) They do not know p or q separately. | y (the encrypted message) e (the encryption key) pq (the modulus to be used) They do not know p or q separately. | y (the encrypted message) d (the decryption key) pq (the modulus to be used) They know p and q separately, which allows them to calculate d. |
| What they do | ||
| Calculate encrypted message y = xe (mod pq) | Knowledge of e and pq is insufficient to be able to decrypt y. | Calculate decrypted message x = yd (mod pq) |
Example
Choose p = 89, q = 151, pq = 13439. If we pick e = 7543, the corresponding decryption key is d = 7.
Choose a four-digit code to encrypt, e.g. 8571 and set up the code as follows:
def powerMod(a, b, c): # raises a to the power of b modulo c res = a % c for i in range(0, b - 1): res = a * res % c return res plainText = 8571 pq = 13439 e = 7543 encryptedMessage = powerMod(plainText, e, pq) print(encryptedMessage)
This should return the encrypted value: 1134.
Now we can verify the system works by using the decryption key of 7 to decrypt the message as follows:
def powerMod(a, b, c): # raises a to the power of b modulo c res = a % c for i in range(0, b - 1): res = a * res % c return res cipherText = 1134 pq = 13439 d = 7 decryptedMessage = powerMod(cipherText, d, pq) print(decryptedMessage)
This code now returns the decrypted message of 8571.
You can show the students that this example works:
and , so
as required.
It is possible to find a decryption key, , such that
whenever the highest common factor of and is .
is calculated by using the Extended Euclidean Algorithm (in the interests of time, showing how this works has been omitted from the lesson).
A student may wonder why an eavesdropper cannot work out for themselves using this algorithm and hence break the code. The answer is that, even though they know the product , they do not know or individually and hence do not know
.
Being able to factorise is a computationally difficult problem and the heart of what makes RSA a very secure system.
Students should implement the example shown before going on to Exercise 3.
Exercise 3
Note
It is possible to automate the process of converting characters to numbers by using the ord() function, which returns the ASCII number for a given character. The chr() function does the inverse operation of turning a number into an ASCII character. Depending on students’ facility with Python, they may choose this approach or simply convert the characters into numbers manually before encrypting and decrypting. The listings below also employ arrays and loops to automate the process of encrypting and decrypting the whole message. Again, this is not essential for the learning objective and can be used or not, depending on students’ proficiency with Python.
Exercise 3a
Use pq=13439 and e = 5077 to encrypt the following message: QUANTUM.
Change each letter into a number and encrypt each in turn (assume A = 1, B = 2, C = 3, etc.)
Answers: [4114, 5656, 1, 7072, 249, 5656, 12523]
Example code listing:
def powerMod(a, b, c): # raises a to the power of b modulo c res = a % c for i in range(0, b - 1): res = a * res % c return res plainText = [ "Q", "U", "A", "N", "T", "U", "M" ] plainTextNumeric = [] for i in plainText: plainTextNumeric.append(ord(i) - 64) pq = 13439 e = 5077 cipherTextNumeric = [] for i in plainTextNumeric: cipherTextNumeric.append(powerMod(i, e, pq)) print(cipherTextNumeric)
Exercise 3b
Use pq=13439 and d = 13 to decrypt your message.
Example code listing:
def powerMod(a, b, c): # raises a to the power of b modulo c res = a % c for i in range(0, b - 1): res = a * res % c return res cipherTextNumeric = [ 4114, 5656, 1, 7072, 249, 5656, 12523 ] pq = 13439 d = 13 plainTextNumeric = [] for i in cipherTextNumeric: plainTextNumeric.append(powerMod(i, d, pq)) print(plainTextNumeric) plainText = [] for i in plainTextNumeric: plainText.append(chr(i + 64)) print(plainText)
The RSA algorithm: summary
The RSA system has two parts: a public key and a private key. The public key is comprised of and . The private key is comprised of and the separate numbers and .
The public key may be safely distributed in the knowledge that it may be used to encrypt messages, but not decrypt them. For example, a bank wishing to verify a transaction from a customer will publish its public key allowing the customer to encrypt some secret information (for example a PIN or the CVC number on a credit card).
The customer can then encrypt this information using
knowing that if a hacker intercepts the encrypted message , they will not be able to retrieve the secret information even if they also have access to the encryption key .
Only the bank, who holds the private key , will be able to decrypt the message, checking the secret information and therefore being able to verify that the person making the transaction is who they say they are.
Breaking the RSA algorithm using a classical computer
To break RSA, we need to know , but to work out requires knowledge of
.
Although is part of the public key, it is not immediately obvious from this what
is. In order to find it we must factorise , but if and are two large primes, factorising is a computationally difficult problem.
The following exercise illustrates the challenge:
Exercise 4
Write a program to factorise the number 900,239,055,271,631. Time how long it takes.
Depending on their facility with Python, the students may need some hints on how to do this. These might include:
- Set up a loop which iterates through all numbers up to the square root of the number we are attempting to factorise;
- Divide the target number by the loop counter. If we get a whole number, we have found a factor;
- To test if the answer is a whole number, check if x / i is equal to x // i (i.e. if dividing x by i gives the same answer as integer division by i);
- If this is the case, exit the loop and print out the result.
Answer: 900,239,055,271,631 = 30,003,209 × 30,004,759
Example code listing:
import time import math timeNow = time.time() x = 900239055271631 i = 2 foundFactors = False while foundFactors == False and i < math.sqrt(x): i += 1 if x / i == x // i: foundFactors = True if foundFactors == True: print(i) print(int(x / i)) print( "Time taken = " + str( int(time.time() - timeNow) ) + " seconds" )
This listing uses the system clock to do the timings – this is not essential: the point of the exercise is simply to show that, even for these comparatively small numbers, it takes the computer a long time to work out factors. On the computer this program was written on, the runtime was 25 seconds!
Conclusion
Factoring is an example of an intractable problem in computing. We have a means of solving the problem (trial division), but as the numbers grow, the number of computations required grows too quickly for any computer to run in a reasonable time. This means it is possible to find a product of primes which no classical computer would ever be able to factorise, regardless of how much processing power it had. In this example, the two primes were 8 digits long. In a real-life implementation of RSA, the primes used would typically be 300 digits long each. Even the fastest supercomputers are unable to factorise numbers of this length in any time that could threaten the security of RSA encryption systems. For this reason, RSA remains a gold standard of internet security, on which many of the world’s online banking systems continue to depend.
However, quantum computing offers a method that could reduce the complexity of the factoring problem down to a speed that could make RSA breakable in real-time. In the next lesson, we will see how Shor’s Algorithm could threaten the security of the RSA method and how this potential threat is driving a revolution in cryptography.
Share this page