代表的な公開鍵暗号であるRSA暗号ですが、
暗号化や復号化の手順自体はかなり簡単で、
プログラムの実装もそこまで苦労することなくできます。
clean-copy-of-onenote.hatenablog.com
ということで、Rで実際にRSA暗号のコードを書いて、
暗号化や復号化を動かしてみましょう。
Rのコード
RSA暗号では、まず公開鍵と秘密鍵のペアを生成し、
公開鍵を使ったメッセージの暗号化と、
秘密鍵を使ったメッセージの復号化を行います。
つまり、鍵生成、暗号化、復号化の主に三つの手順があり、
それぞれを実装すれば暗号で遊べることになります。
鍵の生成
どの公開鍵暗号でのそうですが、
鍵の生成は重要なポイントです。
公開鍵と秘密鍵は、特殊な関係になるようにデザインしておきます。
この特別なデザインのおかげで、
別々の鍵で暗号化、復号化を施すと元のメッセージに戻る
という公開鍵暗号の魔法が実現されます。
RSA暗号の場合は、割り算の余りの世界の関係を使って、
鍵のペアを生成します。
鍵の生成コードは下のようになります。
make.key = function(p=11,q=13){ n = p*q phy = (p-1)*(q-1) e = phy while(gcd(phy,e)!=1){ e = sample(2:phy,1) } tmp = eEuclideanAlgorithm(-phy,e) d = tmp$y if(d < 0){ d = d %% phy } return(list(pub=list(n=n,e=e),sec=list(d=d))) }
p,qは異なる2つの素数で、
鍵を作るために使われます。
公開鍵はnとeで、
nはp,qの積ですが、
eはほぼランダムに選ばれた数値です。
そして、eとある関係を持つように選ばれた
dが秘密鍵となります。
eとdの関係とは、nのオイラー関数の値phyを法として逆数である
というものです。
平たく言えば、eとdの積をphyで割った余りが1であるということですね。
この逆数を見つけるために拡張版ユークリッド互除法を用いています。
拡張ユークリッドの互除法
拡張ユークリッドの互除法では、
xm+yn = gcd(m,n) となるような
x,yを求めます。
下は拡張ユークリッドの互除法と、
最大公約数を求める通常のユークリッドの互除法のコードです。
eEuclideanAlgorithm = function(m,n){ r = c(m,n) k = NULL ans = diag(1,2) while(n != 0){ R = m %% n K = m %/% n m = n n = R ans = ans %*% matrix(c(0,1,1,-K),ncol=2) } return(list(x=ans[1,1],y=ans[2,1])) } gcd = function(a, b){ while(a %% b != 0){ tmp = b b = a %% b a = tmp } return(b) }
暗号化
鍵さえ作ってしまえば、
RSA暗号の暗号化は非常にシンプルです。
暗号化したいメッセージを数値化して、
公開鍵暗号のeで指定されている回数だけメッセージ同士の積をとって、
nで割った余りを求めます。
とは言え、積を繰り返すと途中で桁数が爆発的に増えてしまうので、
積を取るたびに余りを求めるようにしています。
どちらでも計算結果は変わりませんが、
途中で計算できない桁数になることがあるため、
後者で計算するのが安全でしょう。
RSA.encode = function(m,pub){ ans = 1 for(i in 1:pub$e){ ans = (ans * m) %% pub$n } return(ans) # (m ^ pub$e) %% pub$n }
復号化
復号化も暗号化とほぼ同様の操作になります。
違いは、積を取る回数が、
公開鍵のeか、秘密鍵のdかの違いだけになります。
RSA.decode = function(m,key){ ans = 1 for(i in 1:key$sec$d){ ans = (ans*m) %% key$pub$n } return(ans) # (m ^ key$sec$d) %% key$pub$n }
eとdが逆元の関係にあるので、
e回かけてd回かけると丁度1回かける、
つまり、何もしないのと同じ結果になります。
このおかげで、元のメッセージが得られます。
実行例
では、実際に上で実装したRSA暗号を使って、
暗号化、復号化を行ってみましょう。
ここでは、数値の3を暗号化して、
また元に戻すという操作を行ってみます。
m = 3 print(paste("original:",m)) key = make.key() enc = RSA.encode(m,key$pub) print(paste("cryptogram:",enc)) dec = RSA.decode(enc,key) print(paste("decoded",dec))
結果は、次のようになりました。
※鍵の生成に乱数を用いているので、
生成された鍵によって、暗号化された数値の値は異なります。
[1] "original: 3" [1] "cryptogram: 126" [1] "decoded 3"
元々3という数値が、暗号化されて「126」という数値に置き換えられています。
そのあと、復号化により、元の3が得られていますね。
準同型暗号としてのRSA
暗号には、準同型暗号と呼ばれる種類があり、
暗号文のまま計算できるという特徴を持つものがあります。
RSA暗号もこの準同型暗号の一つで、
暗号化されたメッセージ同士でかけ算を行うことができます。
せっかくなので、実際に試してみましょう。
m1 = 2 m2 = 3 print(paste("originals:",m1,",",m2)) key = make.key() e1 = RSA.encode(m1,key$pub) e2 = RSA.encode(m2,key$pub) print(paste("cryptograms:",e1,",",e2)) mul = e1 * e2 print(paste(e1,"x",e2,"=",mul)) dec = RSA.decode(mul,key) print(paste("decode ",mul,"=",dec))
今回は二つの数値2と3を暗号化します。
そして、暗号化された数字同士をかけ合わせて、
ちゃんと、元の数字のかけ算ができることを確認します。
結果は下のようになりました。
[1] "originals: 2 , 3" [1] "cryptograms: 128 , 42" [1] "128 x 42 = 5376" [1] "decode 5376 = 6"
2と3を暗号化することで、
128と42という暗号を得ました。
これらの暗号化された数字同士をかけ合わせると、
5376という数値をえます。
そして、この5376を復号すると、6が得られました。
これは、元々の数字、2,3のかけ算の結果と一致しています。
このようにRSA暗号では、暗号化された世界でかけ算をすることができて、
その結果は、復号化することで元の世界のかけ算結果に戻すことができます。
まとめ
Rを使って、RSA暗号で遊べるように、
コードを書いてみました。
意外とパッケージなどでの実装が見当たらなかったので、
暗号のコードに触れてみたいという人に使ってもらえれば幸いです。