RSA

必要な知識

  • モジュロ演算
  • Fermatの小定理,オイラーの$\varphi$関数
  • 拡張ユークリッドの互除法

概要

素数$p$と$q$を用いて,$n=pq$とする. $e$を公開鍵として,$d$を秘密鍵とする. $e$と$d$は,以下の式を満たす必要がある. $$ed\equiv 1\pmod{\varphi(n)}$$ $e$と$\phi(n)$は互いに素である必要がある. ここで,$\varphi(n)$はオイラー関数である.この値は$n$が異なる素数$p$と$q$の積である場合,次のように求められる. $$\varphi(n)=(p-1)(q-1)$$ 公開鍵$(e,n)$と秘密鍵$(d,n)$を用いて,暗号化と復号化を行う. ここで,$m$は平文,$c$は暗号文である. 暗号化は,以下の式で行う. $$c\equiv m^e\pmod{n}$$ 復号化は,以下の式で行う. $$m\equiv c^d\pmod{n}$$

成り立つ理由

オイラーの$\varphi$関数は,$x$と互いに素な$n$について,次の性質がある. $$x^{\varphi(n)} \equiv 1 \pmod n$$ $ed\equiv 1 \pmod{\varphi(n)}$であるため,次のように復元できることがわかる. $$ c^d\equiv (m^e)^d\equiv m^{ed}\equiv m^{k\varphi(n)+1} \equiv m^{k\varphi(n)}m\equiv (m^{\varphi(n)})^k m \equiv 1^km\equiv m\pmod{n} $$

なお,$k$は$ed=k\varphi(n)+1$が成り立つ整数である.

一般化(Multi-prime RSA)

$n=p_1^{e_1}p_2^{e_2}\cdots p_k^{e_k}$とする.オイラーの$\varphi$関数は,次のように計算できる. $$\varphi(n)=\prod_{i=1}^k(p_i-1)p_i^{e_i-1}$$

プログラム上での求め方

$p,q$

ミラー–ラビン素数判定法を使うことで巨大な素数が生成できる.

$e$

$e=65537$などの素数を用いることが多い.

$d$

$ed\equiv 1\pmod{\varphi(n)}$を満たすような$d$を求める. これは,拡張ユークリッドの互除法を用いて求めることができる.

$c,m$

$c\equiv m^e\pmod{n}$や$m\equiv c^d\pmod{n}$を計算する. このとき,$m^e$や$c^d$は巨大な数になるため,Naiveな計算では間に合わない. 繰り返し二乗法(バイナリ法,ダブリングなど)を用いることで,$O(\log e),O(\log d)$で計算できる.

実装例

from Crypto.Util.number import getPrime, inverse, bytes_to_long, long_to_bytes

def gen_key():
    p = getPrime(1024)
    q = getPrime(1024)
    n = p*q
    phi = (p-1)*(q-1)
    e = 65537
    d = inverse(e,phi)
    return (e,n),(d,n)

def encrypt(m,e,n):
    return pow(m,e,n)

def decrypt(c,d,n):
    return pow(c,d,n)

if __name__ == "__main__":
    (e,n),(d,n) = gen_key()
    m = bytes_to_long(b"Hello World")
    c = encrypt(m,e,n)
    print(long_to_bytes(decrypt(c,d,n)))