なぜ公開鍵暗号は安全なのか
この記事は暗号を理解して解読できるようになろうというシリーズの一部です。シリーズの一覧は次のようになっています。
ここでは公開鍵暗号の入門とそれを応用した技術を理解します。ただ攻撃はしないので攻撃したい人はさらっと読み飛ばすといいかも。
公開鍵暗号
共通鍵暗号の問題は最初に鍵を共有するには物理的に渡す必要があり、それに関わる人を脅されてしまうと鍵を取られて解読・改ざんの一巻の終わりだからです。それを解決させるのが公開鍵暗号です。
公開鍵暗号
暗号化と復号で 別の鍵 を使い、暗号化で使う鍵を 公開 する方式
ex.) RSA暗号, 楕円曲線暗号 など
公開鍵の何が嬉しいのかと言いますと、暗号化と復号で異なる鍵を使い、暗号化で使う鍵を公開することで、誰でも暗号化できて自分以外は復号できない通信が出来ます。
これでは相手から自分への一方向だけの通信しかできませんが、通信相手も公開鍵暗号で通信すれば双方向に通信できます。
そして共通鍵暗号では復号できる鍵を渡す必要がありましたが、公開鍵暗号では一切他人に 復号する権限を渡しません。これが公開鍵暗号の真髄です。これにより共通鍵暗号より真に安全な暗号となります。
こんな公開鍵暗号ですが具体的に暗号を作ろうと思うと比較的難しいです。まずは最も有名な公開鍵暗号である RSA 暗号を紹介しましょう。
RSA 暗号
RSA 暗号とは
例えば
となります。逆に復号は
とこのように平文と一致するので暗号として成り立っていそうです。
まぁこれだと文字列ではないですし、小さい数ですぐ攻撃されるのでぜんっぜん暗号っぽさがありません。次に文字列を暗号化してみようと思います。
例えば This is RSA
という文字列を暗号化すると J\xce(\x15<D\xce\xf43\xcc\xe5\x10V\x80\r\x8d
となり、復号すると This is RSA
となります。
これは文字列を ASCII を用いて数にし、素数
m = This is RSA = 102042840165721490081338177
c = J\xce(\x15<D\xce\xf43\xcc\xe5\x10V\x80\r\x8d = 99433297819769792809975186147967503757
p = 13787377385517866369
q = 9800301481167028771
N = pq = 135120455012699542428087034907156302499
e = 65537
e^-1 = 2799847077334116279129501281763891713
このようにきちんと暗号っぽく機能していそうです。
これを元に手順と Python での実装をサラっと書くと次のようになります。
RSA 暗号 (Rivest-Shamir-Adleman encryption)
- 鍵生成
- 大きな素数
を生成して p, q と N = pq を計算する。 \phi(N) = (p - 1)(q - 1) - 整数
を決めて e を計算する。 d = e^{-1} \pmod{\phi(N)} を公開鍵として相手に渡し、 N, e を秘密鍵とする。 p, q, \phi(N), d - 暗号化
渡された公開鍵を用いて平文に対して m と暗号化し、相手に送る。 c = m^e \bmod N - 復号
持っている秘密鍵を用いて暗号文に対して c と復号する。 m = c^d \bmod N
from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long
p = getPrime(512)
q = getPrime(512)
N = p * q
phi = (p - 1) * (q - 1)
e = 0x10001
d = pow(e, -1, phi)
def encrypt(plaintext):
m = bytes_to_long(plaintext)
c = pow(m, e, N)
cipher = long_to_bytes(c)
return cipher
def decrypt(cipher):
c = bytes_to_long(cipher)
m = pow(c, d, N)
plaintext = long_to_bytes(m)
return plaintext
cipher = encrypt(b"This is RSA")
print(cipher)
plaintext = decrypt(cipher)
print(plaintext)
# This is RSA
お、出てきましたよ鍵が!(倒置法)
この公開鍵と秘密鍵がそれぞれ暗号化する鍵と復号する鍵に対応します。
本当でしょうか?確かめてみましょう!
公開鍵で暗号化
まず公開鍵
これはちゃんと秘密鍵を使わずにできますね。
秘密鍵で復号
それじゃ秘密鍵で復号することができるでしょうか。
秘密鍵
公開鍵で復号
それでは逆に公開鍵で復号できるでしょうか。これが出来てしまうと暗号として機能していないと言えます。復号する為には公開鍵から秘密鍵
秘密鍵で暗号化
最後に秘密鍵で暗号化できるでしょうか。前と同様に秘密鍵から公開鍵を計算できるので暗号化できます。
これらを表にまとめるとこんな感じです。
自分 (公開鍵 + 秘密鍵) | 全員 (公開鍵) | |
---|---|---|
暗号化 | 可能 | 可能 |
復号 | 可能 | 不可能 |
一般の公開鍵暗号
RSA 暗号は素因数分解問題が計算困難なことを用いて暗号が作られました。
素因数分解問題
ある桁数の大きい 2 つの素数の積である合成数が与えられたときに素数 N = pq を見つける問題。合成数のビット数に対して準指数時間で解ける。 p, q
ex.) RSA 暗号, Paillier 暗号, Rabin 暗号など
そして素因数分解問題の他にも離散対数問題という計算困難な問題が知られています。
離散対数問題
位数が大きい巡回群 N について G が与えられたときに g, y\in G となる最小の g^x = y を求める問題。位数のビット数に対して指数時間で解ける。 x\in \mathbb{N}
ex.) DH 鍵共有, DSA, ElGamal 暗号, 楕円曲線暗号など
基本的にこの 2 つの NP 完全な問題を安全性の根拠として沢山の公開鍵暗号が作られます。
公開鍵の応用
公開鍵暗号は通信する以外にも様々な利用方法があります。
鍵共有
共通鍵暗号の一番のネックはそのまま鍵を共有すると盗聴されてしまうことでした。これを公開鍵暗号を用いることで安全に鍵を渡せるようになります。
今でも使われる Diffie と Hellman が考えた画期的な鍵の共有方法を紹介します。
Diffie Hellman 鍵共有
- アリスとボブは巡回群
とその生成元 G を共有する。 g - アリスとボブはそれぞれ秘密鍵
を生成し、公開鍵 x_a, x_b を公開する。 y_a = g^{x_a}, y_b = g^{x_b} - 相手の公開鍵に自分の秘密鍵を掛けた
はアリスとボブだけが知る共有鍵となる。 s - 数学的な値は偏りがある為にハッシュ関数を用いてランダムな数にすることで総当たりに強くする。
署名
暗号化通信では公開鍵で暗号化されたデータを作りましたが逆に秘密鍵でデータを"暗号化"してみるとどうなるでしょうか?
暗号化通信では誰でも暗号文を作れるけれども自分しか読めないものでしたが、これは自分しか作れないが誰にでも読めるデータを作ることが出来ます。
この機能って署名の機能と同じじゃないでしょうか。さらに平文のハッシュ値を署名することで内容が改ざんされていないことを証明できるようにして一般に使えるようにしたものを電子署名という。
Def. 電子署名
本人が信頼された第三者機関である認証局に署名してもらい、その証明書が存在するなら認証されるような仕組み。
現実の署名では手書きのサインや捺印で自分であることを証明をしますがデジタルでは簡単にコピーできてしまうので公開鍵暗号を用いた電子署名が主流です。
電子署名専用のアルゴリズムもあります。
DSA; Digital Signature Algorithm
DLP ベースの署名
- 鍵生成
- 素数
を用いて q と表される素数 p = 2q + 1 を生成する。 p と g\in\mathbb{F}_p を生成して x\in\mathbb{F}_q を計算する。 y = g^x \bmod p - 署名
乱数を生成し、署名したいメッセージ k を用いて m と r = (g^k\bmod p)\bmod q を署名として公開する。 s = k^{-1}(H(m) + xr) \bmod q - 検証
を計算して v = (g^{s^{-1}H(m)}y^{s^{-1}r} \bmod p)\bmod q なら正当な署名となる。 r = v
公開鍵認証
離散対数問題
でもやっぱり実際に解こうとしてみないと難しいといっても指数時間だけ掛ければ解けるという状況が直観的に分からないと思います。素因数分解問題は次回に回すことにしてここでは離散対数問題を実際に解くことを目指します。
離散対数問題 (DLP: Discrete Logarithm Problem)
位数の巡回群 N について G が与えられるので g, y\in G となる最小の g^x = y を求める問題。 x\in \mathbb{N}
暗号では有限体と楕円曲線の 2 種類の巡回群を用いたものが主流です。
- 有限体
の DLP は FFDLP; Finite Field DLP と呼ばれる。巡回群\mathbb{F}_p の位数は\mathbb{F}_p となる。p-1 - 楕円曲線
上での DLP は ECDLP; Elliptic Curve DLP と呼ばれる。巡回群E の位数は Hasse の定理よりE/\mathbb{F}_p に制限される。|\#E/\mathbb{F}_p - (p+1)|\leq 2\sqrt{p}
これらを含め、すべての巡回群を用いた問題を解きます。
Baby-step Giant-step
競プロでいう半分全列挙で解きます。
Baby-step Giant-step
とし、DLP の解 m = \lceil\sqrt{N}\rceil を n で割って m とおく。 n = qm + r \begin{aligned} y & = g^{qm + r} & (q, r\in[0, m-1]) \end{aligned} このとき
, yg^{-r} を全列挙し、どちらかのリストの要素をもう 1 つのリストで検索して g^{qm} となる組を探索し、 yg^{-r} = g^{qm} を求める。 n = qm + r
この計算量は
\rho 法
Pollard's 誕生日のパラドックスを用いて解きます。
Prop. 誕生日のパラドックス
誕生日が同じ 2 人を見つけたいときに確率を超えるには人を何人集めればよいのかという問題です。鳩ノ巣原理から P 人いれば必ず同じ誕生日の人が出てきます。 366 を超えるには 50\% 人で十分です。 23
Proof.
試行回数
よって期待値は大体
このように
- 初期値
と決定的疑似乱数関数a_0 を決めて数列f を生成する。a_{i+1} = f(a_i) -
となるa_i = a_j を発見する。i, j\ (0\leq i<j<N) -
を用いて DLP を解く。a_i, a_j
2 で見つけるのにだいたい
図にすると次のような "
Pollard's
法 \rho
巡回群上の G のもとで初期値 y = g^x とし、関数 a_0 = g については f を G に振り分けて次のように定義する。 G_1, G_2, G_3 f(a)= \begin{cases} ya & (a \in G_1) \\ a^2 & (a \in G_2) \\ ga & (a \in G_3) \end{cases} すると任意の生成元は
と表される。ここで a_i = g^{s_i}y^{t_i} = g^{s_i + xt_i}\ (s_i, t_i \in \mathbb{N}) のとき a_i = a_j \begin{aligned} a_ia_j^{-1} & = g^{(s_i + xt_i) - (s_j + xt_j)} = 1 \\ x &= \frac{s_i - s_j}{t_j - t_i} & \pmod N \end{aligned} となり
が分かる。 x
Pollard-
この派生形として 2 つの数列を作って衝突させる Pollard's Kangaroo 法 (
Pohlig-Hellman
有限体では
Theorem. 有限アーベル群の構造定理
巡回群の位数がと素因数分解できるとき |G| = \displaystyle\prod_{i = 1}^n p_i^{e_{i}} となる。 G \cong \displaystyle\prod_{i = 1}^n \mathbb{Z}/p_i^{e_{i}}\mathbb{Z}
詳細は群論を学んでほしい。これより中国剰余定理から
指数計算法 (Index Calculus Algorithm)
指数計算法は小さな元の離散対数問題を解くことで大きな数の離散対数問題を解く方法です。
-
に対していくつかy を掛けたg が小さな因子基底yg^k を用いてp_j と書けるようなyg^k = \displaystyle\prod_{j = 1}^m p_j^{e_{j}} を見つける。k -
と因子分解できるようなg^{k_i} = \displaystyle\prod_{j = 1}^m p_j^{e_{ij}} をk_i 個以上見つける。n
すると次のように書ける。
これより逆行列を掛けて
計算量は
まとめ
公開鍵暗号の基礎と応用の部分をやりました。暗号の中でもかなり不思議なものだったと思います。私も経験的に納得してるだけで直観的には納得しきれてません。なのでここで納得できてなくても大丈夫です。ここら辺は CTF をすれば咀嚼できるようになれると思います。
次はお待ちかね、CTF の中で最も出題されていると言っても過言ではない RSA 暗号への攻撃です!数学をかなり使うのでそれを補う「計算機代数」の章も参照しながらやっていくといいと思います。
Discussion