RSA (Rivest-Shamir-Adleman) needs no introduction, it is well known and most used public-key cryptosystem that governs our digital lives.
Here is my take, a simple implementation in 10 lines of Ruby code that is neither the best implementation nor the most efficient one but is enough for the purpose of this article.
p = 7 # parameters
q = 11
n = p * q
phi = (p-1) * (q-1)
gcd = ->(a, b) { while(b != 0) do r, a = a % b, b; b = r end; a } # encryption
e = (2..phi).find{|i| gcd.call(i, phi) == 1 }
m = 6
c = m**e % n
d = (2..phi).find{|i| (i * e) % phi == 1 } # decryption
message = c**d % n
m == message ? "YOU ARE A CRYPTOSTAR!" : "YOU SUCK!"YOU ARE A CRYPTOSTAR!
Except lines #5 that involves an algorithm and a while loop, everything else is mid-school math and ** is the exponentiation operator in Ruby.
The curious minds please read simple explanations below.
Integer factorization trapdoor
The very first thing to do is to choose 2 prime numbers, p and q:
p = 7 # parameters
q = 117 11
Next, let's multiply the two numbers:
n = p * q77
Now it's time to define our first function phi:
phi = (p-1) * (q-1)60
which is Euler's totient function defined as number of integers less than n that are coprime with n and has two interesting properties:
- the function is multiplicative and phi(a*b) = phi(a) * phi(b)
- if n is a prime number then phi(n) = n - 1
And finally we have the cryptographic trapdoor function which is the most important cryptographic primitive to understand. Trapdoors are based on asymmetric computation that is easy to do in one direction but it is very difficult to calculate in the opposite direction.
In our case, if one (an attacker) does not know the initial p and q prime numbers it will be very hard to calculate the phi because integer factorization of n is known to be infeasible to calculate for very large numbers.
Message encryption
Now it's time to define our first algorithm, gcd which is the greatest common divisor and is defined as the largest positive integer that divides both a and b.
If single-liner GCD looks scary then here is the formatted version of the Euclidean algorithm.
def gcd(a, b)
while(b != 0) do
r = a % b
a = b
b = r
end
a
endThe e key below is the encryption key and is defined as the smallest value for which gcd(e, phi) == 1 is true. To keep things simple we will just brute-force and find e but keep in mind that this will be very inefficient for large numbers.
gcd = ->(a, b) { while(b != 0) do r, a = a % b, b; b = r end; a } # encryption
e = (2..phi).find{|i| gcd.call(i, phi) == 1 }7
Message encryption is modulo exponentiation of message m at encryption key e.
m = 6
c = m**e % n41
Message decryption
The d key below is the decryption key (or private/secret key) and is defined as multiplicative inverse of e over finite field phi. Simple put in math terms: e * d mod phi ≌ 1.
Again we will just brute-force and calculate the inverse but there are better algorithms to do this, like Extended Euclidean algorithm.
d = (2..phi).find{|i| (i * e) % phi == 1 } # decryption43
Message decryption is another modulo exponentiation using the encrypted cipher c and decryption key d:
message = c**d % n
m == message ? "YOU ARE A CRYPTOSTAR!" : "YOU SUCK!"YOU ARE A CRYPTOSTAR!
Intuition
At first sight it looks like magic right? but if you reason about it, it's very easy.
Starting backwards, with the decryption/encryption formulas:
m = c^d mod n #1
c = m^e mod n #2Let's substitute #2 in #1:
m = (m^e)^d mod nExponentials power rule says that (a^b)^c == a^(b*c) and in the end we only need to prove
m = m^(e*d) mod nor using another form of e*d ≌ 1 mod phi where c is a positive integer
m = m^(1 + c * phi) mod napply power rule again
m = m^1 * (m^phi)^c mod nand last congruence using Euler's theorem that says a^phi(n) ≌ 1 mod n
m ≌ m * 1^c mod nIf the intuition is valid then the following expression stands true, where 7 is e and 43 is d in our little example:
6 == 6**(7 * 43) % 77 ? "YOU ARE A CRYPTOSTAR!" : "YOU SUCK!"YOU ARE A CRYPTOSTAR!