RSA (Rivest–Shamir–Adleman) is a public-key cryptosystem that is widely used for secure data transmission. It is also one of the oldest. The acronym "RSA" comes from the surnames of Ron Rivest, Adi Shamir and Leonard Adleman, who publicly described the algorithm in 1977.
All the programs will get optimized with a better prime sieving function. Julia already uses the sieve of eratosthenes but I will try to find and benchmark prime sieving functions until I find the fastest one. I will then implmenent it into every progam.
We will define p and q small for demonstration purposes:
The euler totient is also known as
Now we need to calculate the public exponent such that...
In order to simplify this calculation I have implemented a function get-public-exponent
which returns the nearest smaller prime of a number n:
(defn prime-rec? [n i ceil]
(if (> i ceil)
true
(if (= 0 (mod n i))
false
(recur n (+ i 2) ceil))))
(defn prime? [n]
(if (= 0 (mod n 2))
false
(let [ceil (Math/sqrt n)]
(prime-rec? n 3 ceil))))
(defn get-euler-totient [p q]
(* (- p 1) (- q 1)))
(defn get-nearest-smaller-prime-of [n]
(if (prime? (- n 1))
(- n 1)
(get-nearest-smaller-prime-of (- n 1))))
(defn get-public-exponent [euler-totient]
(get-nearest-smaller-prime-of euler-totient))
(get-public-exponent 616) ;; => 613
The public key now consists of {public-exponent, N} = {613, 667}
.
Now we must calculate the private exponent such that...
It doesn't make any difference how much we multiply the public exponent because any multiple of
it produces the same outcome since the modular multiplicative inverse stays the same. Therefore we can remove d from the equation which leaves us with this:
The private key consists of {private exponent, N}. We now calculate the private exponent using the extended euclidean algorithm. In order to get the private exponent d
we must calculate the Bézout coefficients.
Bézout's identity — Let a and b be integers with greatest common divisor d. Then there exist integers x and y such that ax + by = d.
Now don't confuse the variable d
in the quote with our variable for the private exponent. Let's calculate the private exponent with this equation:
Now we rearrange the equations above in the following manner (basically resubstitution):
Now we can see that the private exponent d
equals 205.
Let's say you would want to send me message "B" to someone and encrypt the message. The ascii value of 'B' is 66. Now we can encrypt the message using the equation...
...where C is the encrypted message, e is the public exponent and N is the product of the prime numbers. If you have chosen big prime numbers for p and q this modular exponentiation could take very long or even lead to an integer overflow.
In computer programming, an integer overflow occurs when an arithmetic operation attempts to create a numeric value that is outside of the range that can be represented with a given number of digits – either higher than the maximum or lower than the minimum representable value.
In order to get rid of this we can implement a simple function called modpow
which uses a simple algorithm to speed up the process and use less memory. Many languages like Java (modpow in java) already implement such methods.
(defn modpow [base exponent modulus]
(if (= modulus 1)
0
(loop [result 1 e 0]
(if (= e exponent)
result
(recur (mod (* result base) modulus) (+ e 1))))))
The mainflow of the algorithm is as follows:
- It checks if the modulus is equal to 1. If it is, the algorithm returns 0.
- If the modulus is not equal to 1, the algorithm enters a loop.
- Inside the loop, the algorithm multiplies the result by the base and takes the modulus of the product with the modulus.
- The algorithm then increments the e (exponent) by 1.
- The loop continues until the e is equal to the exponent.
- Once the loop ends, the algorithm returns the final result.
Wikipedia visualized this algorithm very well here:
The example base = 4, exponent = 13, and modulus = 497 is presented again. The algorithm performs the iteration thirteen times:
(e = 1) result = (4 ⋅ 1) mod 497 = 4 mod 497 = 4
(e = 2) result = (4 ⋅ 4) mod 497 = 16 mod 497 = 16
(e = 3) result = (4 ⋅ 16) mod 497 = 64 mod 497 = 64
(e = 4) result = (4 ⋅ 64) mod 497 = 256 mod 497 = 256
(e = 5) result = (4 ⋅ 256) mod 497 = 1024 mod 497 = 30
(e = 6) result = (4 ⋅ 30) mod 497 = 120 mod 497 = 120
(e = 7) result = (4 ⋅ 120) mod 497 = 480 mod 497 = 480
(e = 8) result = (4 ⋅ 480) mod 497 = 1920 mod 497 = 429
(e = 9) result = (4 ⋅ 429) mod 497 = 1716 mod 497 = 225
(e = 10) result = (4 ⋅ 225) mod 497 = 900 mod 497 = 403
(e = 11) result = (4 ⋅ 403) mod 497 = 1612 mod 497 = 121
(e = 12) result = (4 ⋅ 121) mod 497 = 484 mod 497 = 484
(e = 13) result = (4 ⋅ 484) mod 497 = 1936 mod 497 = 445
The final answer for c is therefore 445
With this algorithm you never have to calculate the immediate result of
After implementing this method we can just use it inside our encrypt-msg
function:
(defn encrypt-msg [msg public-key]
(def ascii-chars (map int (seq msg)))
(map #(modpow % (first public-key) (last public-key)) ascii-chars))
If we entered "Hello World!" as the function we should now get (282 427 380 380 451 565 522 451 482 380 4 527)
.
In order to decrypt the message we can use the equation...
...where M is the ascii value of our character we sent as a message, C is the new value our encrypted message holds, d is the private exponent and N is the product of the prime numbers.
This uses the exact same modpow
function mentioned previously. In order to decrypt the message we will have to iterate over every integer of the encrypted message and get back it's old ascii char. Then we can join the characters and form a string:
(defn decrypt-msg [msg private-key]
(apply str (map #(char (modpow % (first private-key) (last private-key))) msg)))