Anno Domini MMXXVI Aedificatio · Γλωσσαι · Philosophia Anno Mundi ͵ΖΦΛΕʹ
Thesaurus Imperii

Civilisation, built — in code and cipher, in language and thought.


·3 min read

RSA

What is RSA

RSA was developed by Ron Rivest, Adi Shamir, and Leonard Adleman in 1977. It is based on the assumption that factoring the product of two large prime numbers is computationally difficult.

RSA Mechanism

1. Key Generation

  1. Choose two large prime numbers pp and qq
  2. Compute n=p×qn = p \times q
  3. Compute Euler’s totient ϕ(n)=(p−1)(q−1)\phi(n) = (p - 1)(q - 1)
  4. Choose an integer ee such that 1<e<ϕ(n) 1 < e < \phi(n) and gcd(e,ϕ(n))=1gcd(e, \phi(n))=1
  5. Compute dd such that e×d≡1(mod  ϕ(n)) e \times d \equiv 1 (\mod \phi(n)).
  6. The Public key is (e,n)(e, n) and the private key is (d,n)(d, n).

2. Encryption To encrypt a message MM: C=Memod  nC = M^{e} \mod n

3. Decryption To decrypt a ciphertext CC M=Cdmod  nM = C^{d} \mod n

Vulnerabilities

1. Common Factor Attack

1. Common Factor Attack

In RSA, the modulus NN is the product of two large primes.
However, if two different RSA key pairs happen to share a common prime factor—meaning their NN values have a nontrivial GCD—the private keys can be efficiently recovered using the Euclidean algorithm.
This vulnerability often occurs when poor entropy is used during key generation. In case of that, It satisfies

gcd(N1,N2)=pgcd(N_{1}, N_{2}) = p

so both private keys can be broken. This is a Catastarophic vulnerability that results from weak or reused random number generator during RSA Key Generation.

Example : Is RSA Can be Broken?

Take a look at CTF question at PicoCTF. Our Objective is to crack the ciphertext and get the flag

picoCTF question

I execute encryption program by accessing to the verbal-sleep.pico.net with port number 60275 and nc commands on WSL. We know that We have different Ns. But, Let’s take a look at the code that have provided by the organizer.

from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes

e = 65537

def gen_key(k):
    """
    Generates RSA key with k bits
    """
    p, q = get_primes(k // 2)
    N = p * q
    d = inverse(e, (p - 1) * (q - 1))

    return ((N, e), d)

def encrypt(pubkey, m):
    N, e = pubkey
    return pow(bytes_to_long(m.encode('utf-8')), e, N)

def main(flag):
    pubkey, _privkey = gen_key(1024)
    encrypted = encrypt(pubkey, flag)
    return (pubkey[0], encrypted)

if __name__ == "__main__":
    flag = open('flag.txt', 'r').read()
    flag = flag.strip()
    N, cypher  = main(flag)
    print("N:", N)
    print("e:", e)
    print("cyphertext:", cypher)
    exit()

You can see the input parameter of gen_key function and it has 1024 as fixed-input. The problem is that both of N can be factorized by GCD(Great Common Divisior) and so can find another prime in the first turn.

The solution is as follows:

  1. First, find the GCD of both N values.

  2. Then, find the other prime factor by dividing N by the GCD.

  3. Compute ϕ(n)\phi(n) as (p−1)(q−1)(p - 1)(q - 1).

  4. Compute dd as the modular inverse of ee modulo ϕ(n)\phi(n).

  5. Decrypt the ciphertext using M=Cdmod  nM = C^d \mod n.

  6. Convert the resulting integer into bytes, then decode it as UTF-8.

  7. Finally, you will get the flag!

Solution Code

Here is the solution code. Originally, I wrote the code in JupyterLab and later adapted it to run as a standalone Python script.

from Crypto.Util.number import inverse, long_to_bytes
import math

# First : Check the GCD of N1, N2 then You can get p.(the gcd of n1, n2 is p)
p = math.gcd(N1, N2)
q = N1 // p

# Calculate phi(N)
phi1 = (p-1) * (q-1)

# Calculate d
d = inverse(e, phi1)

# decrypted msg

decrypted_msg = pow(cipher1, d, N1)

# the value is number, we must convert it into plaintext
long_to_bytes(decrypted_msg).decode('utf-8')