前回の続き、こちらのページを参考にした。
OpenSSL で RSA 公開鍵暗号を試してみようWEB ARCH LABO
公開鍵で暗号化→秘密鍵で復号(通信等)
最初はSSL(TSL)で使われ一般的に通信の暗号化で使われる公開鍵で暗号化→秘密鍵で復号する方法をやってみる
暗号化
「openssl-rsautl」コマンドで、文章を暗号化する。「-encrypt」で暗号化を指示、「-pubin」で暗号化を公開鍵で行うように指定、「-inkey」で公開鍵ファイルを指定、「-out」で暗号化した文書の保存先を指定。
$ echo "Hello World" | openssl rsautl -encrypt -pubin -inkey pub.key -out message.dat
$
暗号化されたメッセージを見てみる。
$ hexdump message.dat
0000000 84 d2 5d 29 aa ff 3f ce 78 b7 33 6c 23 90 f1 9d
0000010 e4 f0 2f 9c 82 cc 31 02 00 12 48 4b 90 ef 0d f5
0000020 c0 06 bd 59 d4 ed 13 06 5c 2c 6c 04 96 d0 80 0a
0000030 e7 43 4d 0e e0 bc b4 59 a6 70 1a 3c 05 74 74 b4
0000040 2e 3a 35 51 4a 67 4d 62 ab 81 52 c9 12 1d cd 47
0000050 de ef a6 10 a2 f4 9c fe d7 51 86 cd 95 aa 79 a2
0000060 e8 13 5e 7b 13 d2 bf b8 3a 1f 7e 01 55 30 5f 71
0000070 b0 aa 07 cd 74 14 e2 d7 a5 8b 14 16 b5 e3 c3 37
0000080 73 4f 5e b3 0f 50 15 e6 6d 77 61 37 47 83 9c 4b
0000090 52 3b ca c8 09 b3 81 d7 e2 c1 d4 b3 e6 8a 62 db
00000a0 65 81 26 d2 7d 08 60 44 c8 fa e0 9d b3 02 77 56
00000b0 d2 65 f7 14 e4 f2 c2 45 49 ea b4 7d a3 56 78 5f
00000c0 62 f1 2b ab 3f 7d 66 9c de 1f 3a 56 98 76 05 dc
00000d0 4c 31 80 42 65 c0 d2 c7 43 3d b6 34 ba 04 0e 30
00000e0 65 51 f5 16 a5 4b 7d 86 73 30 e1 34 e8 59 b8 2e
00000f0 f8 24 72 9d 1e 5a 84 0e c1 76 d4 c2 62
00000fd
253バイトのバイナリーデータである。鍵長と同じであることに注目。実はこれも1個のでかい整数である。RSA公開鍵では、最終的に公開鍵の数値で割った時の余りが暗号文となるので、必ず桁数は同じになる。ちなみに暗号化前の前処理で平文はパディングという詰め物をして鍵長と同じ桁数の整数にしてから暗号化を行う。パディングについては後述。
また、桁数が決まっているため、一度に暗号化できるのは桁数以下(パディングあるのでもっと短い)の文字数(サイズ)でなければいけない。その上、RSA暗号鍵方式は暗号化、復号化にとても時間がかかるので通常の通信で本文を暗号化するのには向いていない。なので、RSA暗号鍵は、電子署名とか認証という用途(暗号化対象は短くてよい、公開鍵と秘密鍵という2つの鍵が必要)に使われる。
復号
復号も同じ「openssl-rsautl」コマンドで行う。「-decrypt」で復号を指示、「-in」で暗号化されたファイルを指定、「-inkey」で秘密鍵を指定、なおデフォルトで扱う鍵は秘密鍵なので、秘密鍵のオプションは必要ない。
$ openssl rsautl -decrypt -in message.dat -inkey server.key
Hello World
無事に復号できた。
どんな計算をしてるの?
暗号化、復号の計算は実はとてもシンプル
m:元メッセージ c:暗号文 e:公開指数 n:公開鍵 d:秘密鍵(秘密指数)正確には微妙に違うけどw
計算式自体は暗号化も復号も全く同一、なので、正確には
秘密鍵で暗号化して、公開鍵でそれを暗号化すると、元の平文になる。
公開鍵で暗号化して、秘密鍵でそれを暗号化すると、元の平文になる。
というわけで、暗号化しかしていない。これは面白い。
ちなみにn=p x qでpとqはでかい素数。dはこのpとqのペアがわからないと計算できない。このnの素因数分解が異常に難しいので安全とされているが、段々難しくなっているようで最近は違う数学的な手法が使われることが多くなってる
なお計算式は簡単であるが、実際の処理は大変である、公開鍵での暗号化でも、公開指数は65537に固定されているけど、253バイト(10進で300桁以上の数値)の6537乗を求めるのもそこそこ大変(msまではかからないけど)秘密鍵に至っては10進300桁の数値の300桁の数値の乗数を計算するのであるから、まともに計算してたら秒単位(コンピュータの世界では永遠に近い時間w)かかってしまうかもしれない。でも、天才は世の中にいるので、数msで処理する計算法を編み出した人がいる。「中国の剰余定理」という方法の応用だ。そしてなんと、この定理の最初の記述は中国の南北朝時代の3~5世紀頃に書かれた「孫子算経」だ。古代中国恐るべし
RSA鍵の計算については前出のこのサイトを参照
RSA鍵の要素と暗号化Qiita
ちょっと脱線。SSL(TSL)のネゴシエーションの時など、サーバー側から適当な乱数をクライアントの公開鍵で暗号化してクライアントに送り、クライアントはそれを自身の秘密鍵で復号する。この平文をサーバーに送ることで、クライアントが正しく証明書の保有者であると確認するのだが、この時、悪意あるサーバーが、文書のハッシュの平文を、暗号化せずにクライアントに送るとどうなるだろう。
クライアントは「秘密鍵で復号する」わけであるが、これは上記のように「秘密鍵で暗号化する」のと同義である。そう、「ハッシュを秘密鍵で暗号化したのが電子署名」であるので、悪意あるサーバーはクライアントが気がつかないうちにクライアントの電子署名がついた文書を入手できてしまうのである。
これを防止するのも先程ちょと触れたパディングである。送る平文に特定のマークをつけてから暗号化する。復号した時にこのマーク(パディング)があれば暗号化されたものだし、なければ、送ってきたのは暗号がされてない平文であると区別できる。パディングについて詳しくは以下のサイトを参照
公開鍵暗号の概要シニアエンジニアの庵
RSA暗号wiki
秘密鍵で暗号化→公開鍵で復号(署名のはず)
反対方向も同じなんだから、同じようにOpenSSLを使えばできるんじゃね。ってことで試してみた。
$ echo "Hello World" | openssl rsautl -encrypt -inkey server.key -out messageB.dat
$
おっ、なんかできたっぽい。
暗号文の中身を見てみる
$ hexdump messageB.dat
0000000 59 04 86 6f 2e 21 cb e2 79 91 6a b4 f0 2f 49 94
0000010 7d 3a 2c 8c 1b 39 e2 7c a6 fb 8d 7d 52 a4 55 ad
0000020 95 51 5e 86 0c 3c 34 ff d7 53 8e 75 6c 6b f5 fa
0000030 05 a0 fa 78 25 76 0d a2 d2 93 cc 90 cf e6 e4 09
0000040 2a 5c 77 ea 2b cd 49 b3 ea f3 b8 31 85 4e e5 27
0000050 44 36 82 6f 93 9d 72 c1 bc dc 94 9e 5d e5 f6 b7
0000060 10 ad d2 17 09 03 72 24 45 06 39 8a 30 f7 ee 35
0000070 11 6f 5f 7a 56 3f fc 98 f3 5e fa fb 6c 6c 67 ab
0000080 b6 6c 21 50 e8 04 2b 57 d0 33 67 0f 97 f4 7c eb
0000090 0b 78 63 3f ab a2 03 be 2a 00 cd b9 e7 1a 50 af
00000a0 0b a7 a9 0b 00 ab 17 ee 4a 58 eb 78 86 c9 8a 9a
00000b0 62 b2 81 3a bc ec a4 9d 7f 90 0f 13 e1 ac f7 62
00000c0 b8 d3 9b 75 c6 3e cb 54 14 d5 8c 7d fc 28 db 71
00000d0 7f b1 c8 31 f0 a3 a7 88 41 61 36 84 d2 4c d5 db
00000e0 9d 67 b6 92 4e ba 2a 3f 9a 95 c9 46 08 99 4b ae
00000f0 bd 86 20 cb ad 75 ba af d1 7e 55 eb c6
00000fd
それっぽいのができてる
公開鍵で復号してみる
$ openssl rsautl -decrypt -in messageB.dat -pubin -inkey pub.key
A private key is needed for this operation
秘密鍵が必要だゴラァって怒られた。なんで?!
秘密鍵でやってみる
$ openssl rsautl -decrypt -in messageB.dat -inkey server.key
Hello World
今度はできた。でもこれじゃない(笑)
やりたいのは公開鍵で復号することだ!
う〜ん、何故だと探すこと小一時間、これだこれだ
opensslでの暗号化・復号操作@IT
フムフムなるほど、秘密鍵で暗号化→公開鍵で復号は電子署名なので、「-encrypt」と「-decrypt」じゃなくて「-sign」と「-verify」なのね。前出のパディングの関係かなにかで実装する場合は対称ってわけじゃないんだね
では、早速やってみよう!
「-sign」オプションをつけて署名を指示、出力ファイル名も変えてと、秘密鍵を使うのでオプションの指定はなしで実行
$ echo "Hello World" | openssl rsautl -sign -inkey server.key -out Esign.dat
$
できた。
中身をみる
$ hexdump Esign.dat
0000000 33 f2 5c 05 e0 ab 43 8c 15 47 ae ee cd ef 01 fb
0000010 5c c7 1f 07 bf 1a 7d 43 cf cd 7e 84 26 49 43 01
0000020 90 97 1b 7e 20 d2 9f 27 92 11 8b 80 71 8c 11 c9
0000030 11 1f 5b 2a 26 51 2e 2c ad 10 1e 3a b7 c3 bc aa
0000040 0d b1 f8 01 53 a5 ac 55 d2 e2 6f c1 14 c5 d5 8d
0000050 eb e4 2c f7 dd 03 a4 94 e5 22 8c 58 2d 5c ed c8
0000060 de 63 b1 f5 71 c2 ae 82 ae b3 65 d4 3b e1 6e bd
0000070 00 75 af f6 b0 5a 54 77 36 67 28 df c1 da 6f 5e
0000080 e2 da 30 1a 7c 05 7b 12 bd 47 8b af da f3 b5 f6
0000090 2c 8d c7 51 86 20 66 71 c4 10 7e 98 10 80 68 d8
00000a0 dd 01 fb 76 06 88 ec 20 08 05 6e 89 e9 9f 85 5e
00000b0 a7 2f d9 13 e2 62 83 fa f4 37 c2 76 b3 66 9d db
00000c0 04 b5 c3 7f 92 c8 9d dc bd 70 89 07 48 05 dd 72
00000d0 eb cd e1 2f ea 91 0c c7 ba cd bf 71 04 32 cd a2
00000e0 f4 52 cd b5 fa 25 73 9b 66 62 1b 89 84 23 cc b7
00000f0 b7 7c a7 a7 a1 52 5b 87 26 f3 2c c8 e7
00000fd
できた。あれ、さっきと違う、やっぱりパディングの関係かも。
公開鍵で検証(復号)。「-verify」オプションで検証を指示。「-pubin」で公開鍵を使うこと指定
$ openssl rsautl -verify -in Esign.dat -pubin -inkey pub.key
Hello World
正しく復号された!
verify(検証)といいつつ、ここは実際の処理は検証でなく復号なのね(笑)
ちなみに秘密鍵でも検証できる。
$ openssl rsautl -verify -in Esign.dat -inkey server.key
Hello World
こんな感じ。秘密鍵に公開鍵が隠れてるからできるのだろう。
じゃってことで、上記で-encrypt で暗号化したのを試してみる
$ openssl rsautl -verify -in messageB.dat -pubin -inkey pub.key
RSA operation error
4483001792:error:0407008A:rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding:crypto/rsa/rsa_pk1.c:67:
4483001792:error:04067072:rsa routines:rsa_ossl_public_decrypt:padding check failed:crypto/rsa/rsa_ossl.c:588:
できなかったorz
あっ、でも、やっぱりパディングのエラーだね。暗号化と署名でパディングの形式が違うんだ。なんかスッキリ
OpenSSLライブラリではどうなってるのか
RSA暗号鍵での暗号化と復号は、
「RSA_public_encrypt()、RSA_private_decrypt()」で処理する。
int RSA_public_encrypt(int flen, const unsigned char *from,
unsigned char *to, RSA *rsa, int padding);
int RSA_private_decrypt(int flen, const unsigned char *from,
unsigned char *to, RSA *rsa, int padding);
関数名が示すように暗号化は公開鍵で、復号は秘密鍵でしかできない。
RSA暗号鍵での署名と検証は、
「
RSA_sign()、RSA_verify()」で処理する。
int RSA_sign(int type, const unsigned char *m, unsigned int m_len,
unsigned char *sigret, unsigned int *siglen, RSA *rsa);
int RSA_verify(int type, const unsigned char *m, unsigned int m_len,
unsigned char *sigbuf, unsigned int siglen, RSA *rsa);
リンク先の説明のとおり、RSA_signは秘密鍵、RSA_verifyには公開鍵を渡す。
コマンドライン での実行と違って、検証は検証だ。RSA_verifyに署名と署名の元となったハッシュを渡すと、それが正しいかどうかを検証して結果をint(1なら検証成功)で返す。復号された平文(ハッシュ値)が返ってくるのではないことに注意
HPKIカードで署名を行う場合は、署名はカード内のアプリケーションが行うのでOpenSSLは使用しない。検証はカードから取り出した証明書とOpenSSLのRSA_verifyでおこなう
OpenSSLでの暗号化と復号の話は以上
歯科電子カルテシステム・カルテメーカー は利用料月額16,500円(税込)
MacとWinの両方で利用可能な電子カルテです。介護保険にも対応してます。
カルテメーカーの詳細はカルテメーカー・ホームページまで。
カルテメーカーを実際に動かしてみたいときは評価版をダウンロードできます。