※あくまでこれはネタです
間違いも多々あると思いますが,悪しからず.
ワンタイムパッドとは
別名「バーナム暗号」といい,情報理論の父でお馴染みシャノンさんが証明した情報理論的安全性を持つ暗号であり,条件さえ満たせば絶対に解けない暗号であることが証明されています.
こちらは現在,量子鍵配送で実現を試みられているそうです.
また,RSAなどの現代の暗号は,解読に多大な計算量を必要とする計算理論的安全性を持つものが利用されています.
ワンタイムパッドについては下記の動画が大変参考になります.とても面白いです!
暗号化・復号の手順
-
暗号鍵の生成:
- アリスはランダムな暗号鍵を生成します.この暗号鍵は,送信する予定のメッセージ「HELLO」と長さです.今回は「XYZAB」を生成したとします.
-
暗号鍵の共有:
- アリスは生成した暗号鍵をボブに安全な方法で共有します.
-
メッセージの暗号化:
- アリスは送信したいメッセージ「HELLO」の各文字を,暗号鍵の対応する文字の数値(例えば、A=0, B=1, ..., Z=25)の分文字をずらします.
- 例:
- メッセージ:
H E L L O
- キー:
X Y Z A B
- 暗号文(mod26で計算):
A C O L T
- メッセージ:
-
暗号文の送信:
- アリスは暗号文「ACOLT」をボブに送信します.
-
復号::
- ボブは受け取った暗号文「ACOLT」と共有しておいた暗号鍵「XYZAB」を用いて復号します.暗号文の各文字から対応する暗号鍵の文字の数値を,暗号化の時とは逆にずらします.
- 例:
- 暗号文:
A C O L T
- キー:
X Y Z A B
- 復号後のメッセージ:
H E L L O
- 暗号文:
-
暗号鍵の破棄:
- 最後に暗号鍵を使いまわさないよう,破棄する.
絶対に解けない暗号であるための条件
絶対に解けない」... 最強じゃんと思ってました.
でも通信で聞く技術はSSL/TLS,使っている暗号もAESやChaCha20です,XOR単体で使っている話はあまり聞きません.SSL/TLSはセッション鍵なのでセッション毎使い捨てなので,近くはあるのかもしれませんが,情報理論的安全性を持ってはいないですよね.まぁ訳があるだろうなと思った訳です.
上記参考の動画でも説明がありましたが,絶対に解けない暗号であるためには暗号鍵に以下の条件があります.
- 完全乱数でなければならない
- 少なくとも平文と同じ長さであること
- 使い捨てること(再利用しない)
- 完全に秘匿されること
これを実現するのが困難であるため利用されていません.
コンピュータでは完全乱数を生成するのも困難(なハズ...😅)です.基本的には擬似乱数を利用することになります.こちらは理論的に推測が可能であるが,推測までの計算量が多いという,計算量的安全性を持つことになります.そのため,他を完璧に実装したとしても,その通信の安全性は鍵の計算量に依存することになります.
また,平文と同じ長さの乱数を生成するのにもコストがかかりますし,仮に平文と同じ長さの完全乱数を生成し,安全に共有したとしても,1度使ったら捨てられます.こんなの通信上でやってられません.
ネタ本題
さて,ここからがネタである本題です.今まで述べていた通り,ワンタイムパッドを完璧に実装するのは困難です.けど1メッセージにつき鍵を使い捨てる,量子暗号的なことには憧れがあります.
じゃあ擬似的にやってみよう!と思い,擬似ワンタイムパッドHTTP通信を実装してみました!
アプローチ
TLS1.3のKeyUpdateを利用する
正直,これがやりたかっただけまであります.
KeyUpdateはTLS1.3より追加された,非同期で共通鍵を更新できる機能が追加されました.
これを1パケット送信するたびに行ってしまおうというアプローチです.
実装に際してはopensslを用いました.
サーバ(s_server)
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server.key -out server.crt
openssl s_server -tls1_3 -key server.key -cert server.crt -port 4433
これでサーバ側の準備は完了です.
クライアント(s_client & Ruby)
クライアントにはs_clientを利用します,
こちらKeyUpdateの手段として,双方向の鍵を更新するK
コマンドが用意されている
これを1パケット送信するたびに実行する.これを実現するために下記のプログラムを書きました.
require 'open3'
# 接続情報
hostname = 'localhost'
port = 4433
chunk_size = 1400 # 送信するチャンクサイズ
# 15KBのデータを生成
data = 'aiueo' * (15 * 1024)
# OpenSSL s_clientのプロセスを起動
Open3.popen2("openssl s_client -connect #{hostname}:#{port} -tls1_3 -keylogfile keylog.txt") do |stdin, stdout, thread|
# データを1400バイトごとに送信し、各チャンクごとにKeyUpdateを行う
data.bytes.each_slice(chunk_size) do |chunk|
# データのチャンクを送信
stdin.write(chunk.pack('C*') + "\n")
stdin.flush
puts "パケット送信"
sleep(0.1)
stdin.puts("K")
stdin.flush
end
# 送信完了後に接続をクローズ
stdin.close
end
KeyUpdateをする度に,鍵更新を同期したいためsleep(0.1)で待つようにしています.
また,実行コマンドに-keylogfile keylog.txt
をつけ,共通鍵を出力するようにしておきます.
実行結果(keylog.txt)
114行あったので省略して添付します.
CLIENT_TRAFFIC_SECRET_NとSERVER_TRAFFIC_SECRET_Nの右側が共通鍵を示しています.
詳しくはわかっていないので,有識者の方教えてください.
また,参考URLを添付しておきます.
# SSL/TLS secrets log file, generated by OpenSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 6dd9c2cae8f6d143fdca74d2c9134ab7f48b28b8caed3c5fa1a94b5c6ace424d44d240deb4190b103921e7ace844f2e8
EXPORTER_SECRET 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 50a1a74fe687799096c24172cfa2e0e2342ec6713a3973cc18f8def126e01ef95499bfd44d2f81546476d5019822f3ec
SERVER_TRAFFIC_SECRET_0 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 04e006fa9209da11c3b4105bedb57ed14e102ddb5de39e1ca32196eefde1d1ba0da729cd91a528ec481016a50775bd25
CLIENT_HANDSHAKE_TRAFFIC_SECRET 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 1676d750ce99e08aa5448d11358d4d77a2a1ee79e8f5244d9000d3bd6c1648a277133ed66873f518722ba8686f170a03
CLIENT_TRAFFIC_SECRET_0 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 8e032b62b69b1a302555d80cf68e6569c19130a0665783e09667e789fef6f9e92ade8179c1ba951dcb3a969918fb5e6
CLIENT_TRAFFIC_SECRET_N 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 7fb41364f07208421a08cd8956ffbd6bbe823fe9d6140a8c1ebb69cefd9c2f40e2dea947d92264757fe80ed46940ad4d
SERVER_TRAFFIC_SECRET_N 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 b5ae87b7697054ed28f040f6e3b15ae43b0d97875813ca73151572f7ff4331d679c5240b0f4588232ae6d377991f524e
... 省略 ...
CLIENT_TRAFFIC_SECRET_N 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 560f32a5c7769f9c72777a3b98316d06e263d4e55688205c6a0bb8b287362915d6b734b6043c54703b62fe605c7cf082
SERVER_TRAFFIC_SECRET_N 328c947ce2d9204ceec04376026beb1c9b7ce272f1ab833128395d67e4dc13a6 5b784a43fb701e23eb4979965ed1ea0b5defd0249a0b705a5c4fb01cc3724b6e210d25930f53497a3d99071f0b986595
よくわからないとは言ったものの,共通鍵は更新されていることはわかる.
これで1送信毎に鍵を更新したワンタイムパッドのようなHTTPS通信が実現できた!?
安全性の話
もちろんネタなので,情報理論的安全性には程遠いです.
最初にプリマスターシークレットなどの様々なデータを共有するので,TLSが提供する鍵交換の安全性に全て委ねられます.当たり前ですが計算量的安全性ですね.
1送信ごとに鍵が更新される設計上,1送信の解読にかかる時間が延びるため,通常のセッション鍵よりは安全性が高くなっているのかな?
鍵更新時の通信解読や,更新時のアルゴリズムの安全性を考慮すべきですね.こちら次第で有用かどうかも変化するかも?
また,致命的なのは,通信が激遅になることです.実際には使い物になりませんね.
まとめ
ワンタイムパッドについての学習と好奇心のみで書きました.
問題点だらけなのは承知してます💦
もちろんネタ投稿ですが,もっと賢い人が応用を考えればなんかうまくいく気もする...
まだまだ勉強が必要だと感じました.