ベルリンのITスタートアップで働くジャバ・ザ・ハットリの日記

日本→シンガポール→ベルリンへと流れ着いたソフトウェアエンジニアのブログ

公開鍵暗号とRSA暗号の仕組み

公開鍵暗号の仕組みをまとめることにした。
先日作成して公開したエンジニア向けのパズル8は公開鍵暗号の仕組みを使っている。RSA暗号のコード書いてデバッグして、暗号の仕組みをかなり理解したので、せっかくだからまとめた。

公開鍵暗号

金庫の中にに大事なモノを保管する時、まずは金庫を閉めて鍵をかける。すると鍵を持っている人にしか開けられなくなる。これが通常の鍵の仕組み。
ただネットを介した通信での暗号方式では少し事情が異なる。
まず送信者が金庫を閉めて鍵を使って鍵をかける。そしてそれを受信者に届ける際には金庫と鍵を送ってもらう必要がある。しかし金庫と鍵を同時に送るのは危険。途中でその鍵が見られたりコピーされたりしたら、金庫に入れる(暗号化すること)意味が無くなってしまうからだ。大事なモノに鍵をかけて送るのに、その鍵を送る方法が無防備だとまったく無意味になってしまう。

そこで数学者の叡智により編み出されたのが公開鍵暗号方式。それは金庫を閉めるための鍵と金庫を開けるための鍵があり、それらをペアで使う方式。ここで言う金庫を閉めるための鍵を「公開鍵」、金庫を開けるための鍵を「秘密鍵」という。

公開鍵と秘密鍵

公開鍵

平文(誰にでも読める文章データ)に暗号をかけるための鍵
"I love you"という平文があって、それを公開鍵を使って暗号をかけると"43048, 20523, 600.."というような数字の羅列になる。
ただし公開鍵があっても、暗号を元の平文に戻すことはできないので誰に見られても問題無い。

秘密鍵

暗号文を元の平文に戻すための鍵
先ほどの例で言うと"43048, 20523, 600.."を"I love you"に戻すための鍵。他人の手に渡ると危険なので、大切に保管する必要がある。ただし暗号の受信者は秘密鍵を自分で持っておくだけでよい。受信者は送信者へ秘密鍵を渡す必要はない。

RubyでRSA暗号

ここからはコードを使って実際にRSA暗号をかけて、それを復号してみる。

文字は数字に変換

まず暗号では基本的に全ての文字が数字に置き換えられる。
"Jabba the Hutt"と書かれた文字列の数字変換はRubyであれば.each_codepointを使えばできる。

$ irb
irbを起動

irb> text = "Jabba the Hutt"
=> "Jabba the Hutt"
irb> text_array= text.each_codepoint.to_a
=> [74, 97, 98, 98, 97, 32, 116, 104, 101, 32, 72, 117, 116, 116]

この74,97...となっている配列の数字は暗号化したのではなく、単に文字を数字(コードポイント)に置き換えただけ。コードポイントとは、1文字を表す整数のコード。
これは簡単に元の文字列に戻せる。

irb> 74.chr
=> "J"
irb> text_array.map{|i| i.chr}.join("")
=> "Jabba the Hutt"

暗号化をするにはこの数字の羅列を鍵を持っている人にしか分からない方法で変換すること。
そのためにはRSA暗号では乗数の表を使う。

乗数の表

横軸がかける数
縦軸が乗数
2の列を上から見ていけば一番分かりやすい。2,4,8,16,32,64とお馴染みの数字が縦に並んでいる。

1 2 3 4
1 1 2 3 4
2 1 4 9 16
3 1 8 27 64
4 1 16 81 256
5 1 32 243 1024
6 1 64 729 4096
7 1 128 2187 16384
8 1 256 6561 65536
9 1 512 19683 262144
10 1 1024 59049 1048576

10までしか載せていないのは、たくさん載せるとあまりに数が大きくなり過ぎてややこしいだけだから。

RSA暗号ではとても大きい2つの素数をかけた数字を元にその数で割ったあまりだけを表に入れる。
ここでは例をカンタンにするためにそのとても大きな2つの素数をp= 7、q= 19とする。実際はコンピューターがカンタンに計算できないぐらいにデカい。
pとqを掛けた数字はpq = 7✕19 = 133となる。

先ほどの乗数表を133で割ったその余りを記載する。コードで言うなら%もしくはmodにあたる。つまりこれで表の中のどんなに大きな数字も全て0から132のどれかの数字になる。
するとちょっと不思議なことがあるのが分かる。

1 2 3 4 5 6 7 8 9 10 11 12 13 14
(1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14
2 1 4 9 16 25 36 49 64 81 100 121 11 36 63
3 1 8 27 64 125 83 77 113 64 69 1 132 69 84
4 1 16 81 123 93 99 7 106 44 25 11 121 99 112
5 1 32 110 93 66 62 49 50 130 117 121 122 90 105
6 1 64 64 106 64 106 77 1 106 106 1 1 106 7
7 1 128 59 25 54 104 7 8 23 129 11 12 48 98
8 1 123 44 100 4 92 49 64 74 93 121 11 92 42
9 1 113 132 1 20 20 77 113 1 132 1 132 132 56
10 1 93 130 4 100 120 7 106 9 123 11 121 120 119
11 1 53 124 16 101 55 49 50 81 33 121 122 97 70
12 1 106 106 64 106 64 77 1 64 64 1 1 64 49
13 1 79 52 123 131 118 7 8 44 108 11 12 34 21
14 1 25 23 93 123 43 49 64 130 16 121 11 43 28
15 1 50 69 106 83 125 77 113 106 27 1 132 27 126
16 1 100 74 25 16 85 7 106 23 4 11 121 85 35
17 1 67 89 100 80 111 49 50 74 40 121 122 41 91
18 1 1 1 1 1 1 77 1 1 1 1 1 1 77
(19) 1 2 3 4 5 6 7 8 9 10 11 12 13 14
20 1 4 9 16 25 36 49 64 81 100 121 11 36 63
21 1 8 27 64 125 83 77 113 64 69 1 132 69 84
22 1 16 81 123 93 99 7 106 44 25 11 121 99 112
23 1 32 110 93 66 62 49 50 130 117 121 122 90 105
24 1 64 64 106 64 106 77 1 106 106 1 1 106 7
25 1 128 59 25 54 104 7 8 23 129 11 12 48 98
26 1 123 44 100 4 92 49 64 74 93 121 11 92 42
27 1 113 132 1 20 20 77 113 1 132 1 132 132 56
28 1 93 130 4 100 120 7 106 9 123 11 121 120 119
29 1 53 124 16 101 55 49 50 81 33 121 122 97 70
30 1 106 106 64 106 64 77 1 64 64 1 1 64 49
31 1 79 52 123 131 118 7 8 44 108 11 12 34 21
32 1 25 23 93 123 43 49 64 130 16 121 11 43 28
33 1 50 69 106 83 125 77 113 106 27 1 132 27 126
34 1 100 74 25 16 85 7 106 23 4 11 121 85 35
35 1 67 89 100 80 111 49 50 74 40 121 122 41 91
36 1 1 1 1 1 1 77 1 1 1 1 1 1 77
(37) 1 2 3 4 5 6 7 8 9 10 11 12 13 14
38 1 4 9 16 25 36 49 64 81 100 121 11 36 63

全ての数字は予想不可能な変化を繰り返しているが、
1行目と同じ内容が繰り返えされている箇所がカッコを付けた19行目と37行目にある。

つまりこの表の中ではどんな数でも19乗や37乗して133のmodをとれば元の数に戻るのだ。

元に戻る乗数

この19,37行目の数字の出し方はp-1とq-1の最小公倍数の倍数に1を足した数になる。
p-1とq-1の最小公倍数とはつまり18。
ここでは18の倍数に1を足した数。19, 37, 55....行目の数は1行目と同じ数字になるという法則がある。
この性質を暗号に使ったのがRSA暗号。
ある文字(数字)を何乗かして、予想のつかない数字にしてしまう。この乗数が公開鍵になる。
でまたその数字をきっちり19,37,55,のように元に戻るようにまた何乗かして元に戻す。この乗数が秘密鍵になる。

表で言えばまず1行目から10行目まで行って、訳の分からない数字にして暗号化する。その後19行目まで行って元に戻すイメージ。

公開鍵の作成

まず(p-1)と(q-1)の最小公倍数を出す。ここではLとする。

irb> p = 7
=> 7
irb> q = 19
=> 19
irb> L = (p-1).lcm(q-1)
=> 18

Lが18と出た。別にコード書かなくても6と18の最小公倍数は18で当たり前なんだけど。

公開鍵の条件は1より大きくL(=18)よりも小さい。
しかも公開鍵とLとの最大公約数は1であること、つまり互いに素であることが条件。
コードにすると以下のようになる。
ここでは公開鍵をpublicとする

irb> public = [*(2..18)].each {|i| break i if (i.gcd(18)==1) }
=> 5

公開鍵は「133で割ったの余りの表を使って5乗すること」になる。

秘密鍵の作成

秘密鍵の条件は
秘密鍵✕公開鍵=L✕n+1となる数。
ここでは秘密鍵をprivateとする。

irb> private = [*(2..18)].each { |i| break i if ((public * i) % 18 == 1) }
=> 11

秘密鍵は「133で割ったの余りの表を使って11乗すること」になる。

表を見ても分かるように、平文の数字を公開鍵で5乗するとよく分からない数字になって暗号化できる。
元に戻すにはその数字を11乗してしまえば5✕11=55で55乗、ということは18の倍数+1の55行目と同じく元の平文に戻る。

暗号化と復号化

まずは冒頭のJaba the Huttを公開鍵(public=5)で暗号化する。

irb> encrypted_array = text_array.map { |i| i ** 5 % 133 }
=> [44, 13, 91, 91, 13, 128, 51, 111, 5, 128, 116, 129, 51, 51]

やってることは元の数字を5回かけて133の余りを出しただけ。それだけでもう暗号化されて、元の数字とはまったく異なる数になる。

で、これを秘密鍵の11と133を使って復号する。

irb> decoded_array = encrypted_array.map { |i| i ** 11 % 133 }
=> [74, 97, 98, 98, 97, 32, 116, 104, 101, 32, 72, 117, 116, 116]
irb> decoded_array.map { |i| i.chr }.join
=> "Jabba the Hutt"

やってることは暗号の数字を11回かけて133の余りを出すだけ。それだけで、元どおり。

この例では小さな数の素数を使っているので解読できなくもない。これを100桁以上ある大きな素数を使えばもうコンピュータでもなかなか解読しにくくなる。この辺りのコンピュータでも解読しにくい理由については気が向いたら「公開鍵暗号とRSA暗号の仕組み - その2」でも書くつもり。
 
 
RSA暗号は気付いていてもいなくても誰もが毎日なにかの形で使っている。めちゃくちゃに頭のいい数学者達の叡智の結晶が、こんなにシンプルな形にまとめられていることに感心せずにはいられない。本当にすごい技術というのは究極的にはシンプルなのだな、と。

このRSA暗号の技術をそのままパズルにしたのがほとんどのエンジニアには解けるパズル8。もしご興味があれば、ぜひ挑戦してみてください。
 
 

暗号技術入門 第3版

暗号技術入門 第3版

ちょっと暗号のことネットで調べたらすぐにこの本がヒットする定番の中の定番の本。いちいち書評書いても意味が無いほどの定番の暗号本。

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com