[PYTHON] Implement RSA

The memo that I studied. There weren't many articles about implementing RSA that works well with large prime numbers.

Purpose

Implement 256bit RSA encryption with the minimum configuration.

How to do RSA

See wikipedia for details, and write only the flow.

  1. Determine the number of bits. (256bit this time)
  2. Make large prime numbers $ p, q $. (128bit, half of 256)
  3. n=pq
  4. Determine a relatively prime number $ e $ that is less than $ (p-1) (q-1) $.
  5. Find $ d $, which is $ ed \ equiv 1 \ pmod n $.

Now you are ready. Encryption is

  1. Make plaintext $ m $ ($ m <n $)
  2. The ciphertext $ c $ is $ m ^ e \ pmod n $

Decryption is OK with $ m = c ^ d \ pmod n $.

There is no such difficult calculation, but the point is

--Exponentiation is heavy ――How to make big prime numbers $ p, q $? -How do you decide $ e $? --What about $ d $?

Exponentiation is heavy

In RSA, powers are multiplied by hundreds of digits, so exponential multiplication cannot be done obediently. ** Fast exponential operation ** makes it faster. Furthermore, since what we want to find is the surplus of the power, not the power itself, we can optimize this point as well. Let's say you want to calculate the remainder of $ a $ raised to the $ b $ power by $ n $. First, convert $ b $ to a binary number. Let each digit of the binary number be $ b_i $ $ a^b = a^{\sum 2^{i}b_i}=\prod a^{2^ib_i} $ It will be. here, $ a^{2^{i+1}}=a^{2^i}*a^{2^i} $ So you can calculate the next digit with one multiplication. What's more, you want to calculate the power value divided by $ n $, so if you take too much at each step of the multiplication, you only have to deal with values smaller than $ n $. As an example, if $ a = 3, b = 11, n = 21 $, then $ b = (1011) _2 , that is, $ b=2^3+2^1+2^0 $$

As $ a^b=3^{2^3+2^1+2^0}=3^{2^3}*3^{2^1}*3^{2^0} $

3^{2^0}=3
3^{2^1}=3^{2^0}*3^{2^0}=9
3^{2^2}=3^{2^1}*3^{2^1}=81
3^{2^3}=3^{2^2}*3^{2^2}=6561

Divide each by $ 21 $ and take too much if it exceeds 21 during the calculation. $ 3*9*6561\equiv (3*9)*9\equiv 6*9\equiv 12 $

main.py


def modular_exp(a, b, n):
    res = 1
    while b != 0:
        if b & 1 != 0:
            res = (res * a) % n
        a = (a * a) % n
        b = b >> 1

    return res

How to make a big prime number

Apparently it's quite difficult to generate prime numbers directly. Practically, it seems easy to ** generate random numbers until they hit a prime number **.

Random number generation

Since the number of bits is specified, it is OK if 0,1 are randomly arranged for that number.

main.py


def gen_rand(bit_length):
    bits = [random.randint(0,1) for _ in range(bit_length - 2)]
    ret = 1
    for b in bits:
        ret = ret * 2 + int(b)
    return ret * 2 + 1

The maximum bit and the minimum bit are set to 1 instead. This is because the maximum bit is an odd number so that the number of digits is not reduced. The reason for odd numbers is that I want prime numbers now, and even numbers are not prime numbers.

Primality test

I want to determine if the huge random number $ p $ is a prime number. $ P $ is so big that it's too slow to divide it in order. ** [Miller-Rabin Primality Test](https://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A9%E3%83%BC%E2%80%93%E3% 83% A9% E3% 83% 93% E3% 83% B3% E7% B4% A0% E6% 95% B0% E5% 88% A4% E5% AE% 9A% E6% B3% 95) ** Seems to be often used. The algorithm is also on wikipedia, so implement it as it is. Simply put, we check the requirements for prime numbers many times and determine that if they all pass, they are probably prime numbers, and if they don't pass even one, they are composite numbers. ** Since this is a probable primality test algorithm, composite numbers may be judged as prime numbers. ** ** However, it seems that the false positive is about $ \ frac {1} {4} $ in one check, so if you do it dozens of times, it will be almost 0 in practical use, so there seems to be no problem.

main.py


def mr_primary_test(n, k=100):
    if n == 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    d = n - 1
    s = 0
    while d % 2 != 0:
        d /= 2
        s += 1

    r = [random.randint(1, n - 1) for _ in range(k)]
    for a in r:
        if modular_exp(a, d, n) != 1:
            pl = [(2 ** rr) * d for rr in range(s)]
            flg = True
            for p in pl:
                if modular_exp(a, p, n) == 1:
                    flg = False
                    break
            if flg:
                return False
    return True

So, prime number generation is like this.

main.py


def gen_prime(bit):
    while True:
        ret = gen_rand(bit)
        if mr_primary_test(ret):
            break
    return ret

How to decide e?

It is quick to make it a prime number because it can be relatively prime with $ (p-1) (q-1) $.

p = gen_prime(128)
q = gen_prime(128)
e = gen_prime(128)

I think there is no problem with that. wikipedia states that $ 65537 $ is often used. It seems that a fixed constant is fine.

d?

You can use the extended Euclidean algorithm. Originally for natural numbers $ a, b $ $ ax+by=gcd(a,b) $

It is an algorithm to find $ x, y $ such that. (gcd is the greatest common divisor) Use this for $ e, (p-1) (q-1) $. Since the greatest common divisor is 1 from the definition of $ e , $ ex+(p-1)(q-1)y=1 $$

Dividing both sides by $ (p-1) (q-1) $ $ ex\equiv 1 $ So this $ x $ is $ d $. The extended Euclidean algorithm was in here, so I copied it. $ x_0 $ is $ d $.

main.py


def xgcd(b, n):
    x0, x1, y0, y1 = 1, 0, 0, 1
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return b, x0, y0


def gen_d(e, l):
    _, x, _ = xgcd(e, l)
    return x % l

Run

The rest will work if you prepare plaintext.

main.py


bit_length = 128
p = gen_prime(bit_length)
q = gen_prime(bit_length)
e = gen_prime(bit_length)
d = gen_d(e, (p - 1) * (q - 1))
n = p * q

m = 123456789
c = modular_exp(m, e, n)  #Cryptogram
m_ = modular_exp(c, d, n)  # 123456789

It worked even with 1024bit. (It took about 20 seconds) Actually, it is dangerous to encrypt the same plaintext many times, so it seems that a random number is added at the end (deleted at the time of decryption), and if m is too small, it is padded.

Recommended Posts

Implement RSA
Implement the REPL