SSL

ずっとある程度まとめて勉強したことが無かった、 SSL について軽く調べてみた。あんま深く調べてないので真に受けんで下さい。

とりあえず使う

まずはとりあえずクライアントとサーバーを作る。 OpenSSL というとオレオレ認証局を作るのがめんどいという感じだけど、 Debian だと

 # apt-get install ssl-cert
 # make-ssl-cert generate-default-snakeoil

こんだけで /etc/ssl/certs/ssl-cert-snakeoil.pem と /etc/ssl/private/ssl-cert-snakeoil.key ができる。ちゃんと作るなら /usr/share/ssl-cert/ssleay.cnf を編集して使えばいいんだけど、これもまぁすごい簡単で、いずれにせよずいぶん楽にできるんだなぁと思った(なんか昔 expect とかで作ってたなー)。

でまぁそれでできたファイルを /tmp の下に置いたとして、サーバ例 in Ruby:

 #!/usr/bin/env ruby
 
 require 'openssl'
 require 'socket'
 include OpenSSL
 
 srv = TCPServer.new(4444)
 if ARGV[0]
   ctx = SSL::SSLContext.new(ARGV[0])
   ctx.cert = X509::Certificate.new(File.read('/tmp/ssl-cert-snakeoil.pem'))
   ctx.key = PKey::RSA.new(File.read('/tmp/ssl-cert-snakeoil.key'))
   srv = SSL::SSLServer.new(srv, ctx)
 end
 while s = srv.accept
   cont = s.gets
   s.print(cont)
   s.close
 end

引数で SSLv23|SSLv2|SSLv3|TLSv1 のどれかを指定すると SSL なサーバになって、つけないと普通の TCP サーバになる。 SSLContext の引数は省略すると SSLv23 。

クライアント例 in Ruby:

 #!/usr/bin/env ruby
 
 require 'openssl'
 require 'socket'
 include OpenSSL
 
 s = TCPSocket.new('localhost', 4444)
 if ARGV[0]
   ctx = SSL::SSLContext.new(ARGV[0])
   ctx.ca_file = '/tmp/ssl-cert-snakeoil.pem'
   ctx.verify_mode = SSL::VERIFY_PEER
   s = SSL::SSLSocket.new(s, ctx)
   s.connect
 end
 s.puts("hogehoge")
 puts s.read
 s.close

同じく引数で通信方式を指定している。 ctx 作って ca_file と verify_mode に値代入するのはやらんでも動くけど、せっかくなんでやった方がいいんじゃないかと思われるたぶん。

通信方式

SSL っていうとなにやら色々通信方式がある。 SSLv2 ってのは古い形式で、なにやらセキュリティホールが見つかってるとか書いてあった。 SSLv3 は新しいヤツでそれほぼ同じ形で標準化したのが TLSv1 。実際 OpenSSL は SSLv3 と TLSv1 は同じ扱いみたい。 SSLv23 ってのはどっちも使えるよ、という話みたい。

SSL はいくつかのプロトコルの集合体(鍵共有、認証、暗号化、ダイジェスト、かな)だけど、そのそれぞれのプロトコルで使うアルゴリズムは色々差し替えられるので結構なパターン数がある。 openssl の cipher オプションで使える暗号化形式一覧がわかる。

 % openssl ciphers -v ALL@STRENGTH
 ADH-AES256-SHA          SSLv3 Kx=DH       Au=None Enc=AES(256)  Mac=SHA1
 DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
 DHE-DSS-AES256-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(256)  Mac=SHA1
 AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
 ...

とかすると使える暗号化形式を強度順で一覧できる。強度つーてもどういう理由で決めてるのかは知らんけど。で、プロトコルが四つあるから暗号名が四つ並ぶわけだけど、なんか RSA とかは省略されるとかまぁなんかよくわからんので、右っかわが大事。略語はそれぞれ Kx = key exchange, Au = authentication, Enc = Encryption, Mac = Mac digest とかみたい。

それぞれ

逆順で書いていく。以下特にあんまり調べてない。 Wikipedia に全部載ってるから正しく/詳しく知るならそっちをどうぞ。

ダイジェストってのは、通信する時にハッシュ値みたいなのを送っておいてデータが壊れてないか確認する。たぶん改竄とかデータおかしくなった時(TCPで止まる気もするけど)対策。 SHA1MD5 の二択みたい。

本文の暗号化は共有鍵暗号でやる。共有鍵暗号っていうのは一番簡単なのは、「データは XOR 27 かけて読んで下さいねー」と取り決めしておくような暗号。 XOR かけるだけとかだと出現頻度とかで一瞬で破られるので色々工夫があるらしい。 OpenSSL だと、 AES, 3DES, DES, DES, RC4, RC2 あたりが名前として見える。 RC2 と RC4 は秘密だったけど匿名でバラした子がいて公開情報になったらしい。 DES は古いらしくて、それを改訂しようと NIST とかいうのが標準化したのが AES 。 DES を 3 回するから安全らしいのが 3DES 。よくわからんけど AES でいいんじゃね、という感想を持ちました。

このへんの AES 策定の時の話は何回見ても面白い。

http://h2np.net/bit/aes-rep.html

http://h2np.net/bit/aes2/index.html

鍵共有と認証は公開鍵暗号が必要な部分。鍵共有は共有鍵暗号の鍵を交換するのに公開鍵暗号を使う。全部公開鍵でやっても良さそうに思うけどたぶん公開鍵暗号は遅いからとかじゃないかな。認証は公開鍵暗号でデジタル署名ができるからべりさいんとかの認証局に適当に認めてもらったら身元を証明できるとかなんとか(説明になってない)。

鍵共有は RSA と DH ってのが主にあるみたいで、 RSA素因数分解とたぶん同じ感じの問題だろうと考えられてる RSA 問題っていうのの不可逆性を利用してうんたら(たぶん)。 DH ってのは Diffie-Hellman の略らしく、離散対数問題の不可逆性を利用してうんたら。違いは DH の方がわかりやすくて RSAフェルマーの小定理とかが登場して少し難しいこと。だから個人的には離散対数の方が好きです。なんか見た目が綺麗で感心した記憶があるし。だからどうした。

あと離散対数問題を実際にどう暗号に使うかってのは herumi さんが書いておられてわかりやすかった気がします。

http://homepage1.nifty.com/herumi/crypt/crypt01.html#QUIZ

認証は RSA と DSS の二択で DSS の方が離散対数の方…かと思ったんだけど DSS は当初は DSA のことだったんだけど、今は DSS = DSA + RSA + ECDSA のことらしい

速度 (レイテンシ)

openssl speed とかいうのがあるみたいだけどよくわからんので自分で測定して感覚を掴んでみたいと思った。

でとりあえず localhost と 1000 回くらい hogehoge とか echo サーバーに送って返事を受信するだけの簡単なベンチをしてみて、結果は以下みたいな感じだったんだけど、

 0.771862 TCPSocket
 6.825988 ["DES-CBC-MD5", "SSLv2", 56, 56]
 6.852499 ["EXP-RC4-MD5", "SSLv2", 40, 128]
 6.896539 ["EXP-RC2-CBC-MD5", "SSLv2", 40, 128]
 7.110548 ["RC4-MD5", "SSLv2", 128, 128]
 7.592685 ["RC2-CBC-MD5", "SSLv2", 128, 128]
 8.015529 ["RC4-MD5", "TLSv1/SSLv3", 128, 128]
 8.417717 ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56]
 8.424664 ["AES256-SHA", "TLSv1/SSLv3", 256, 256]
 8.443360 ["DES-CBC3-MD5", "SSLv2", 168, 168]
 8.534704 ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168]
 8.663834 ["AES128-SHA", "TLSv1/SSLv3", 128, 128]
 8.993231 ["RC4-SHA", "TLSv1/SSLv3", 128, 128]
 16.143871 ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56]
 51.640470 ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168]
 51.647685 ["EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56]
 52.671211 ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128]
 57.752796 ["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256]

なんか色々よくわからんことがあって、まずいくつか例外が飛んじゃって選べないのがあった(たぶんRSAの証明書渡してるからDSAな認証ができないとかだと思う)。あとなんか SSLv3 と TLSv1 はまぁ似たような結果だったのは良かったんだけど、 SSLv3 のヤツを SSLv23 でやると SSLv23 でやった方が速くなったりして謎だなーと。まぁよくわからんけど上の結果はとりあえず、速いのと遅いのがあるなーというくらいの認識でいいんじゃないかと思った。もちろん遅いと言ってもカスみたいなサイズのデータを送るだけの例で遅いだけなので、もっとたくさん送ったら速いのかもしれないし、暗号の強度が強かったりするのかもしれないし、まぁよく知らんです。でもまぁさっきの強さ順のヤツと見比べてみると、こういう細かいの送る用途だと、速い方のグループにいる AES256-SHA あたりがいいんじゃないかとか思ったけど知らんです。

で、 localhost とのやりとりだと現実的じゃないので実際どんくらいの overhead なんかなーと、 shinh.org と実家で 10 往復くらいさせてみたベンチ。

 0.911451 TCPSocket
 1.433355 ["DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168]
 1.435176 ["AES256-SHA", "TLSv1/SSLv3", 256, 256]
 1.514165 ["RC4-SHA", "TLSv1/SSLv3", 128, 128]
 1.533170 ["RC4-MD5", "TLSv1/SSLv3", 128, 128]
 1.574204 ["AES128-SHA", "TLSv1/SSLv3", 128, 128]
 1.596982 ["EXP-RC4-MD5", "SSLv2", 40, 128]
 1.600494 ["DES-CBC-MD5", "SSLv2", 56, 56]
 1.617288 ["DES-CBC-SHA", "TLSv1/SSLv3", 56, 56]
 1.626607 ["RC2-CBC-MD5", "SSLv2", 128, 128]
 1.634951 ["DES-CBC3-MD5", "SSLv2", 168, 168]
 1.708413 ["EXP-RC2-CBC-MD5", "SSLv2", 40, 128]
 1.720872 ["RC4-MD5", "SSLv2", 128, 128]
 1.863379 ["EDH-RSA-DES-CBC3-SHA", "TLSv1/SSLv3", 168, 168]
 1.985764 ["EXP-EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 40, 56]
 2.014566 ["EDH-RSA-DES-CBC-SHA", "TLSv1/SSLv3", 56, 56]
 2.108500 ["DHE-RSA-AES128-SHA", "TLSv1/SSLv3", 128, 128]
 2.123336 ["DHE-RSA-AES256-SHA", "TLSv1/SSLv3", 256, 256]

なんか要するに大差ないということがよくわかるのであった。まぁ相変わらず AES256-SHA でいいんちゃうのって感じですが。こっちは試行回数少ないからさっきのベンチよりさらにあてにならない。

速度 (スループット)

shinh.org に対して TCP, SSLv2, SSLv3 のそれぞれで "hogehoge"*10000 というすごく圧縮しやすそうな 80kB の文字列を送ってみた。結果は TCP (0.86sec), SSLv2(1.00sec), SSLv3(0.25sec) ってとこだった。 SSLv2 って圧縮しないんかな。

次に適当な text file (34kB) だと、 TCP(0.36), SSLv2(0.53), SSLv3(0.35) だった。

次に適当な jpeg image (27kb) だと、 TCP(0.33), SSLv2(0.45), SSLv3(0.45) って感じ。

まぁなんか「SSL は圧縮かかるから生 TCP より速かったりする」とたまに聞くのが本当なのかが知りたかっただけなので、 SSLv3 に関しては本当、 SSLv2 は圧縮せんのかな?というあたりがわかったのでまぁ良いとする。

まとめ

  • 少なくとも Ruby だと使うのは割に簡単。
  • なんか遅めのアルゴリズムとかもあるので、クラサバ両方専用で作る時は各アルゴリズムベンチマークはしてみた方がいいかも。
  • つってもインターネット介するとアルゴリズムの違いは速度的にはあんまり問題にはならんみたいだけど。
  • 少なくとも SSLv3 はなんか圧縮のおかげで TCP と互角に戦える感じかなぁ。
  • こんな情報はアテにならないので Wikipedia を見よう。

openssl コマンドでクラサバ作り

忘れてた openssl コマンド。アホみたいな数のコマンドがあるけど、とりあえずクラサバ用のコマンドとして s_client と s_server がある。

 % openssl s_client -connect www.google.com:443
 ...
 GET /
 ...

とかそんな感じ。暗号方式指定するなら -tls1 とか。あと -CAfile /tmp/ssl-cert-snakeoil.pem とかつけると認証局の確認ができる。

あとは s_server の方はサーバーを作れて、

 % openssl s_server -cert /tmp/ssl-cert-snakeoil.pem -key /tmp/ssl-cert-snakeoil.key

とかで手動で応答を返すサーバを作ることができる。タイプ速度とかに自信がある人は手動検索エンジンとか作るといいんじゃないかと思う。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h