不倫温泉旅行とOpenSSLで理解する公開鍵暗号

ざっくりと公開鍵暗号を使ってデータのやり取りをする方法を学びます。

状況

AliceとBobが不倫をしており、夫の不貞を疑っているBobの妻、Eveがいるとしましょう。AliceとBobは盗聴を試みるEveにバレないよう、安全に2泊3日の温泉旅行に関する打ち合わせメッセージを送受信したいとします。Aliceから通信を始め、BobはEveに対して「出張である」と嘘をつくものとします。

パスワード付きZip(共通鍵暗号)

Aliceは睦言を暗号化してBobに送り、復号するための鍵も同一の経路で送ります。つまりパスワード付きZipをメールで送り、パスワードを別メールで送るといういわゆるPPAPがこれです。

OpenSSLでやると以下のようになります。インストールしてない場合はインストールしてください。

# 乱数から鍵を作る。乱数で無くてもテキストで「pasuwa-do」でも何でもいい
openssl rand -base64 48 > common.key

# メッセージの作成
echo "次いつ会える?😄" > message.raw

# AES128という共通鍵暗号アルゴリズムで暗号化する
openssl enc -e -aes128 -pbkdf2 -kfile common.key -salt -in message.raw  > message.enc

# 暗号化された結果を見る
cat message.enc
U2FsdGVkX19kOtwulpgQI+PPvuVXyZ5lEcHD+MMktXGwm8B1kHlLrrOrlCwAZ4Ev

はい、メッセージが暗号化されました。「次いつ会える?」がU2Fs…という人間には読めない文字列に変わっています。では、この鍵 common.key と暗号化メッセージ message.enc をBobに送ります。

Bobは以下の手順で復号します。

openssl enc -d -aes128 -pbkdf2 -kfile common.key -in message.enc -base64
次いつ会える?😄

メッセージが正常に復号化できました。ここでやっていることはパスワード付きZipと大差ないです。

さて、盗聴を試みるEveはBobのスマホの画面ロックを突破しようとPINを入力します。皮肉にもBobが設定していたPINはEveの誕生日でした。それはさておき、Eveは暗号化されたメッセージが添付されたEメールと、鍵が添付されたEメールの両方をGETできたので、同様の手順で復号できてしまいます。

このように、鍵と暗号化メッセージを同一の経路で送ることは実質的に平文を送っていることと大差ないです。PPAPでも、もし暗号化ZIPを盗聴者が取得できているとしたら、続いて送られる暗号化キーも取得できていると考えるのが自然でしょう。電子メールを盗聴する手段は色々あるとおもいますが、いずれの方法にしても特定のメールだけ盗み見れて他のメールは見れない、という前提は不自然です。

AliceとBobについては「密会したときに口頭で共通鍵を教えたら良いやんけ」と思われるかもしれないですね。それは正しいです。暗号文と鍵が別経路で送られるのでEveはスマホでメールを盗み見しつつ、密会現場で口頭により伝えられる鍵も手に入れないとメッセージの復号が出来ないのでよりセキュアになると言えます。が、この方法はいくつか問題があります。

  • 鍵が人間が理解できるテキストに限定されるため総当りで突破される危険性が上がる
  • 一般的なインターネット上での暗号通信には適さない

です。特に二点目に関しては、暗号化メッセージをするのにいちいち事前に相手にパスワードを伝えるために直接対面で会う、なんてことは面倒臭すぎてやってられませんね。なのでここでは考えないものとしますが、もしみなさんが不倫なさる場合(民法第770条をお読みになった上で自己責任で行う場合において)はご参考になりましたら幸いです。

公開鍵と秘密鍵

暗号文と復号化のための鍵をセットで送ることが危ない、ということが共通鍵暗号方式でのやりとりによって明らかになりました。ここで登場するのが公開鍵暗号方式というやつです。

この方式では、暗号化するための鍵(公開鍵)と復号化するための鍵(秘密鍵)が別です。暗号化に使った公開鍵で復号することはできず、復号には秘密鍵が必要になります。秘密鍵で暗号化することも出来ますが、その場合は公開鍵によって復号化しなければなりません。公開・秘密の2つは両者の鍵に何か本質的な違いがあるわけでなく、「これは公開用に使う鍵」「これは公開せず秘密に持っておく鍵」と決めて間違えないように名前を付けているようなものです。

ではやってみましょう。まず、AliceとBobはそれぞれ公開鍵と秘密鍵を作ります。

# Aliceの秘密鍵
openssl genrsa 2048 > key-A.priv

# Aliceの公開鍵
openssl rsa -in key-A.priv -pubout -out key-A.pub

# Bobの秘密鍵
openssl genrsa 2048 > key-B.priv

# Bobの公開鍵
openssl rsa -in key-B.priv -pubout -out key-B.pub

そして、AliceとBobは公開鍵を互いに交換します。この公開鍵は「公開」と言っている通り、人に見られても問題ないという運用を行います。なので公開鍵はEveに見られても問題ありません。

一方で秘密鍵は誰にも見られないところに保管しておき、自分だけが使います。これは当然ながら、Eveに見られてはいけません。

AliceがBobにメッセージを送るとき、以下のようにします。暗号化の鍵としてBobの公開鍵を使うところがミソです。

echo "次いつ会える?😄" | openssl rsautl -encrypt -pubin -inkey key-B.pub > message.enc
cat message.enc
��ɉԑ2��ix�...

暗号化されたメッセージをBobが受信したら、Bobは自分だけが見れるよう安全に保管しているBobの秘密鍵で復号します。

openssl rsautl -decrypt -inkey key-B.priv -in message.enc
次いつ会える?😄

やったぜ!

AliceとBobが送受信するデータのすべてがEveによって盗聴されたとしても、Eveは暗号を解読できません。Eveがメッセージを解読するためにはBobの秘密鍵が必要です。

ということはBobの秘密鍵が暴露されないように管理しなければならないわけですが、これは共通鍵をバレないように配送するよりはずっと簡単でしょう。

安全な鍵配送

公開鍵暗号方式でお互いに安全にやり取りを行うことができました。でもまたすぐに困ったことになってしまいます。Aliceが密会温泉旅行から帰った次の日の朝のことです。

cat <<EOF > message.raw
> おはよう😄
> 
> 温泉旅行たのしかったね✨
> Bobくんあんなにお酒飲むところ初めてみちゃった😊
> 温泉行ってたときは楽しかったし幸せだったけど、家に帰ってくるとまた
> 現実が始まっちゃうな〜って感じで辛いね💧
> EOF

cat message.raw | openssl rsautl -encrypt -pubin -inkey key-B.pub > message.enc
RSA operation error
140125049571136:error:0406D06E:rsa routines:RSA_padding_add_PKCS1_type_2:data too large for key size:../crypto/rsa/rsa_pk1.c:124:

失敗しました。data too large for key size だそうです。まじ?こんな小さいテキストファイルなのに?

でもそうなんです。残念ながら公開鍵暗号方式であるRSA暗号は鍵の長さ以下のデータしか暗号化できません。今回は512bitで作りましたが、これだと117バイトまでしか暗号化できないそうです。

じゃあどうしましょうか…。

一つは、メッセージを細切れにしてそれぞれ暗号化していくという方法があります。でもこの方式だともう一つ問題があります。それは公開鍵暗号方式は計算量が多いということです。


image from Comparative Analysis of DES, AES, RSA Encryption Algorithms

DES、AESは共通鍵暗号ですが、RSAは公開鍵暗号です。これを見ると顕著にRSAが遅い事がわかりますね。

Aliceは考えました。どうしよう、暗号化・復号化が遅すぎる…。これだと温泉旅行で撮ったあんな写真やこんな写真やこういう動画をBobくんに送りたいのに、めちゃ時間がかかってしまう…。でも平文であんな写真やこんな写真を送ってそれをもしEveさんが盗聴してしまったらもし離婚訴訟が提起されたときに不貞の証拠物件として提出されてしまう…。盗聴されても安全なのは公開鍵暗号だけど大容量データは送れず、大容量データを送るには共通鍵暗号を使うしかない…。

せや!共通鍵でデータを暗号化して、その鍵だけを公開鍵で暗号化して送ったらええんや!

# 大容量サンプルデータの作成
dd if=/dev/urandom of=sensitive_photo.data bs=1M count=100

# その場限りの共通鍵を作成
openssl rand 48 > common.key

# 共通鍵で大容量データを暗号化
openssl enc -e -aes128 -pbkdf2 -kfile common.key -salt -in sensitive_photo.data > sensitive_photo.data.enc

# Bobの公開鍵で使用した共通鍵を暗号化
cat common.key | openssl rsautl -encrypt -pubin -inkey key-B.pub > common.key.enc

BobはAliceより sensitive_photo.data.enccommon.key.enc を受け取り、これらを用いてデータを復号します。

# Bobの秘密鍵で共通鍵を復号化
openssl rsautl -decrypt -inkey key-B.priv -in common.key.enc > common.key.dec

# 復号化した共通鍵でデータを復号化
openssl enc -aes128 -d -pbkdf2 -kfile common.key.dec -in sensitive_photo.data.enc > sensitive_photo.data.dec

# オリジナルデータが復号できた!
diff -q sensitive_photo.data.dec sensitive_photo.data

データの復号にはBobの秘密鍵が必要なので、Eveは sensitive_photo.data.enccommon.key.enc を傍受してもデータの復号は行えません。Eveがセンシティブなフォトを証拠物件として提示することを阻止できました。やったね!

まとめ

みなさんがインターネットを使用してアドレスバーに鍵マークがついたサイトにアクセスしているとき、裏側ではここで書いたようなことに基づく暗号化が行われています。

ただ、ここに書いてあることはほんの一部で、実際に運用されている通信とはだいぶ異なるところがあります。

中でも、特にTLS通信を説明する上ではほぼ必須である「通信相手の真正性を確認する」というところが抜けています。その他のトピックとしては、「データが通信経路で改ざんされていることを検知する」というのもあります。

元気があったらそういう記事も書いてみようかなと思いますが、今の所元気が無いので元気が出るように下部のいいねボタンを押しといてください。よろしくお願いいたします。