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で作ったりすることもあります。短い場合は、「はしょったな」と思ってください。

"e is 65537"とは何なのか

RSA秘密鍵を作ると、末尾に [e is 65537 (0x10001)] と表示される。このeとはpublicExponentと呼ばれる値である。

この値の意味は、あとでRSA暗号の実装の話をするときに一緒に説明するので、今は置いておく。

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では、以下のように鍵を生成する。WikipediaRSA暗号の項目の引き写しになるけど:

  • 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オプションで入力する公開鍵ファイルを明示的に指定している。

なお、出来上がるmessageファイルはバイナリファイルとなる。必要ならば、base64エンコードすると良いだろう。

復号

Bobからmessageファイルを受け取ったAliceは、大事にしまっておいた秘密鍵(private-key.pem)を使って復号する。

$ cat message | openssl rsautl -decrypt -inkey private-key.pem
HELLO

無事、挨拶を受け取ることができました。おめでとう。