opensslでRSA暗号と遊ぶ
opensslとRSA暗号についてちょっと調べてみようかな、と思った。
まずRSA暗号とは、
ってことを理屈としては理解しているけど、実際にopensslコマンドで作った鍵ファイルの中身がどうなっているのか? ということまで踏み込んだことが無かった。
というわけで、ちょっとその辺をコマンド叩きながら遊んでみることにする。
はじめに:opensslの操作について
opensslコマンドは増築に増築を重ねすぎており、もはやそびえ立つ××のようである。ヤヴァいことになったレベルで機能てんこ盛りのコマンドなので、サブコマンドとして機能名を指定して使うことになる。
openssl command [ command_opts ] [ command_args ]
上例の「command」には、RSA秘密鍵を作るgenrsa、乱数を作るrand、X.509証明書を扱うx509などなどを指定する。というか、どう考えても詰め込みすぎだ。
また、コマンド実行時の入力/出力ファイルの指定は、UNIX式のリダイレクト < と > 以外に、 -in と -out オプションを指定することもできる。この両者の記述法は混在できるため(たとえば入力は-inで指定して、出力は > 、とか)、この辺の流儀が人によって違うのでさらに混乱を招いている(気がする)。
本稿ではなるたけシンプルにするため以下のルールで書くことにしている。
- 入力出力ファイルは、 -in / -out オプションは使わず、リダイレクトのみで書く。
- 入力出力ファイルは、コマンドとオプションを書いた後の一番後ろに、入力→出力の順で書く。
例。
# こういうのは分かりにくいので書かない。 $ openssl rsa -in private-key.pem -pubout > public-key.pem # こう書くルールにする。 $ openssl rsa -pubout < private-key.pem > public-key.pem
はじめに:もうひとつ注意
この記事では例示のために秘密鍵の中身を晒していますが、鍵を実際に運用する際、これは絶対にやってはいけないことです。秘密鍵(Private Key)は自分だけしか知らない、公開鍵(Public Key)は見せても良い。間違えないように。
秘密鍵の作成
ではまず、これが無ければはじまらないRSA秘密鍵を作ろう。genrsaコマンドで作ることができる。まずはオプション無しのデフォルトの動きを見てみよう。
$ openssl genrsa > private-key.pem Generating RSA private key, 1024 bit long modulus ......++++++ ......++++++ e is 65537 (0x10001)
私がMacOSで使っているopenssl 1.0.1e 11 Feb 2013では、デフォルトのbit数は1024bitだった。これは2013年現在では、もはや若干不安の残るbit数である。というわけで、秘密鍵は2048bitで作成しよう。これは単にオプションに数字を入れればいいだけだ。
2048bitでの作例
$ openssl genrsa 2048 > private-key.pem Generating RSA private key, 2048 bit long modulus ..................................................+++ .........+++ e is 65537 (0x10001)
なお、この記事中では文字数の節約などのため、40bitで作ったりすることもあります。短い場合は、「はしょったな」と思ってください。
pemとは何なのか
出来上がったファイルの名前は、ここではprivate-key.pemとしている。拡張子の.pemは、PEM形式というフォーマットで生成しているからこう名付けている。なおファイル名は何でも良いので、目的によっては server.key など拡張子として.keyを使うこともある(Apacheのデフォルトconfはそうですね)。
なぜ人によって拡張子の名付けが違うかと言うと。
- ファイル形式がPEMなんだから。と思う人は .pem を使う。
- これは鍵ファイルだから。と思う人は .key を使う。
どちらの言い分も一理あるが、SSL証明書も扱おうとすると、あれはすべてPEM形式なので、鍵ファイルも証明書ファイルも全部.pemになってしまうので若干ややこしいと個人的には思う(.key派は、.crtなど拡張子を見ればファイル種別がある程度分かる)。
あなたがどちら派でも構わないが、特に.pem派の場合は、なるたけファイル名に見てすぐ意味が分かる名前を付けよう。
で、作った素数は秘密鍵ファイルのどこに書かれてるの?
さて秘密鍵を作ったのだから、さっそく素数を見てみたい。大きな素数の積と聞いたが、それはこのファイルのどこにあるんだろう? 普通にcatしても、以下のように意味が分からない文字列が出るだけだ。
$ cat private-key.pem -----BEGIN RSA PRIVATE KEY----- MIGJAgEAAhkM6qw2097tLLrrxyvroAP1KpFST7zqDGs3AgMBAAECGQU3fqiCN8Yo y7HQdmzDbRuk6F/1M0m8iukCDQOyZBmc2iP+oQa3HbUCDQN+dZKDR5Sgyqf12LsC DQNzL0dXwZtlvRBvQyECDQH8T9yfdoJkjka9VBkCDQM93ozWDaFqvCR3J9I= -----END RSA PRIVATE KEY-----
さてRSA秘密鍵から人が読める形式に出力するには、rsaコマンドの -text オプションを使えば良い。以下のような出力になる。なお、これは見やすくするため196bitの鍵なので短め。
$ openssl rsa -text < private-key.pem Private-Key: (196 bit) modulus: 0c:ea:ac:36:d3:de:ed:2c:ba:eb:c7:2b:eb:a0:03: f5:2a:91:52:4f:bc:ea:0c:6b:37 publicExponent: 65537 (0x10001) privateExponent: 05:37:7e:a8:82:37:c6:28:cb:b1:d0:76:6c:c3:6d: 1b:a4:e8:5f:f5:33:49:bc:8a:e9 prime1: 03:b2:64:19:9c:da:23:fe:a1:06:b7:1d:b5 prime2: 03:7e:75:92:83:47:94:a0:ca:a7:f5:d8:bb exponent1: 03:73:2f:47:57:c1:9b:65:bd:10:6f:43:21 exponent2: 01:fc:4f:dc:9f:76:82:64:8e:46:bd:54:19 coefficient: 03:3d:de:8c:d6:0d:a1:6a:bc:24:77:27:d2 writing RSA key -----BEGIN RSA PRIVATE KEY----- MIGJAgEAAhkM6qw2097tLLrrxyvroAP1KpFST7zqDGs3AgMBAAECGQU3fqiCN8Yo y7HQdmzDbRuk6F/1M0m8iukCDQOyZBmc2iP+oQa3HbUCDQN+dZKDR5Sgyqf12LsC DQNzL0dXwZtlvRBvQyECDQH8T9yfdoJkjka9VBkCDQM93ozWDaFqvCR3J9I= -----END RSA PRIVATE KEY-----
わー、なんかいっぱい出てきた。で、2つの素数ってどれ?
modulus
一番最初に出てくる[modulus]が、2つの素数の積である。見慣れぬ形式になっているが、これは10進で書くと長すぎるので16進で書いて、見やすくするため間をコロンで繋いでいるだけである。たとえば、このmodulusの下4ケタは6b:37だから、6b37(16進数)→27447(10進数)である。
この素数の積はバレても良い値なので、あとで公開鍵を作った後にも入っているはずである、覚えておこう。
prime1とprime2
お待ちかねの2つの素数は、このパラメタだ。この例だと、
prime1: 03:b2:64:19:9c:da:23:fe:a1:06:b7:1d:b5 prime2: 03:7e:75:92:83:47:94:a0:ca:a7:f5:d8:bb
この2つである。先ほどと同様に16進数で書かれているので、直したければ10進数にすると良い(ケタが多すぎてたぶん挫折するけど)。以下のように、もっと鍵のbit数を小さくして40bitくらいにすれば、普通の計算機でも検算できるレベルになる。
$ openssl genrsa 40 > private-key.pem $ openssl rsa -text < private-key.pem Private-Key: (40 bit) modulus: 883643360161 (0xcdbd3fa3a1) publicExponent: 65537 (0x10001) privateExponent: 56993035025 (0xd450cb311) prime1: 941933 (0xe5f6d) prime2: 938117 (0xe5085) exponent1: 497433 (0x79719) exponent2: 611793 (0x955d1) coefficient: 267819 (0x4162b) writing RSA key -----BEGIN RSA PRIVATE KEY----- MDACAQACBgDNvT+joQIDAQABAgUNRQyzEQIDDl9tAgMOUIUCAweXGQIDCVXRAgME Fis= -----END RSA PRIVATE KEY-----
例えばこの例なら、prime1 x prime2 の積は 941933 x 938117 = 883643360161(modulus)になっていることが確認できる。
公開鍵の作成
秘密鍵ができれば、そこから公開鍵を作るのは簡単である。RSAの原理から言っても、2つの素数を隠してその積だけ見せる……と考えれば簡単だと分かりますね。
公開鍵は、秘密鍵を入力ファイルとして、rsaコマンドの-puboutオプションで出力できる。
$ openssl rsa -pubout < private-key.pem > public-key.pem writing RSA key
これで出来上がった public-key.pem が公開鍵ファイルである。さっそく中身を見てみよう。
$ openssl rsa -text -pubin < public-key.pem Public-Key: (196 bit) Modulus: 0c:ea:ac:36:d3:de:ed:2c:ba:eb:c7:2b:eb:a0:03: f5:2a:91:52:4f:bc:ea:0c:6b:37 Exponent: 65537 (0x10001) writing RSA key -----BEGIN PUBLIC KEY----- MDQwDQYJKoZIhvcNAQEBBQADIwAwIAIZDOqsNtPe7Sy668cr66AD9SqRUk+86gxr NwIDAQAB -----END PUBLIC KEY-----
先ほどの秘密鍵と見比べると分かるけど、積(Modulus)とpublicExponentだけしか入ってない。なるほど、素数のprime1などは入っていないぞ。ということで、このファイルは公開してもいいんだな。と確認できた。
eとかprimeとかpublicExponentとか
見た通り、RSA公開鍵は2つの素数の積(Modulus)とpublicExponentの2つである。
一方、秘密鍵は2つの素数がキモという話をしてきたけど、RSAの実装としては実は[2つの素数が秘密鍵]では無い。暗号・復号の都合上、実際の秘密鍵はprivateExponentである。
RSAでは、以下のように鍵を生成する。WikipediaのRSA暗号の項目の引き写しになるけど:
- 2つの素数をp,qとする。これはopensslではprime1,prime2である。
- 素数の積pqをnとする。これはopensslではmodulusである。
- φ(n)=(p-1)(q-1)とする。
- eを、φ(n)と互いに素な数から適当に選ぶ。これはopensslではpublicExponentである。eは候補がいくつもあるが、実装上はe=65537で決め打ちのようである。なぜこの値かと言うと、65537は2進数で書くと10000000000000001となり、ビットがほとんど立っていない。暗号化する際はe乗するので、なるたけビットが立っていない方が高速に計算できるからである。
- dを、eとの積がφ(n)を法としたとき1となるよう選ぶ。つまり d * e ≡ 1 (mod φ(n))。これはopensslではprivateExponentである。
暗号化
元をe乗して、nで割った余り(mod n)が暗号文となる。つまりaが元ならば a^e mod n 。
復号
暗号文をd乗して、nで割った余り(mod n)がもとの値となる。つまりbが暗号文ならば b^d mod n 。
実際に暗号化・復号してみる
ではせっかく秘密鍵と公開鍵を作ったので、これで暗号化して、そして復号してみよう。シナリオとして、Aliceの公開鍵(public-key.pem)を取得したBobが、通信文 "HELLO" を送ることを考える。
暗号化
Bobは暗号文を送るために、rsautlコマンドの-encryptオプションを利用すれば良い。
$ echo "HELLO" | openssl rsautl -encrypt -pubin -inkey public-key.pem > message
今まで入力ファイルはリダイレクトで書いてきたけど、rsautlでは標準入力は平文を受け取るのに使うため、-inkeyオプションで入力する公開鍵ファイルを明示的に指定している。
復号
Bobからmessageファイルを受け取ったAliceは、大事にしまっておいた秘密鍵(private-key.pem)を使って復号する。
$ cat message | openssl rsautl -decrypt -inkey private-key.pem HELLO
無事、挨拶を受け取ることができました。おめでとう。
サーバ証明書
作成した秘密鍵を元にオレオレ証明書を作る手順は、次の記事にまとめました:オレオレ証明書をopensslで作る(詳細版) - ろば電子が詰まっている