Piping Chat 0.3.0以降のエンドツーエンド暗号化と公開鍵認証の流れ
#Piping_Chat #Piping_Server #セキュリティ #エンドツーエンド暗号化 #公開鍵認証 #楕円曲線ディフィー・ヘルマン鍵共有
ざっくりと
SSHの公開鍵認証で使われている手法と同じような方法を使われた。
以下のPRで実装された。
なぜ認証したいのか?
エンドツーエンド暗号化していても、なぜ通信相手を認証したいのか?
流れ
AliceとBobでForward secrecyにエンドツーエンド暗号化通信して、公開鍵認証で本当に相手なのかを検証する。
短くするため、AliceがBobが本物か検証する。BobがAliceを検証するのは、以下の流れの名前をひっくり返せばなるはず。
上位のリストだけ追えば、フローがわかるようになっている。
以下の入れ子になっているリストは具体的な説明。
Aliceは事前に信頼できるソースからBobのRSA公開鍵(= Bob_RSA_Pub)を手に入れる。
BobはAliceに楕円曲線ディフィー・ヘルマン鍵共有(ECDH)の公開鍵を2つ送る
1つは、セッションID生成に使うため(= Bob_Session_ECDH_Pub)
もう1つは、通信路を暗号化するための公開鍵(= Bob_Encrypt_ECDH_Pub)
上記の2つの鍵の生成方法は全く同じ。内部が乱数だから値が異なるだけ。
形式は、JSON Web Key (JWK) で送られる
コードは「鍵生成部分のコード」に掲載
AliceもBobに楕円曲線ディフィー・ヘルマン鍵共有(ECDH)の公開鍵を2つ送る
送る鍵の種類もBobと同様
Alice => Bob, Bob => Aliceで鍵送る順序は、どっちが先でも構わない
AliceはBob_Session_ECDH_Pubを使ってセッションIDを生成するSession_ID
ECDHで交換できる秘密鍵のがセッションIDになるため、Session_IDはAliceもBobも同じものになる。
暗号化通信用の鍵と全く違うものを使って(鍵は独立しているので)セッションIDを生成するため、セッションIDの流出と暗号通信は無関係になる。
Session_IDの生成はSHA256(JSON文字列化(ECDHで得られる共有鍵のJWK))な感じである
実際のセッションID生成コード
セッションIDの生成は、ECDHで鍵を共有していると思うよりランダムバイト列を共有したと思ったほうが、理解しやすいと思う。
AliceはBob_Encrypt_ECDH_Pubを使ってECDHの共有鍵(Encrypt_Key)を生成する
Encrypt_KeyはBobも同じものが生成できる。
Encrypt_Keyを使ってこれからの通信はすべて暗号化される。
Bobは自分のRSA秘密鍵を使って、Session_IDに署名する。
Bobは署名したSession_IDをAliceに送る
AliceはBobの署名されたSession_IDを予め入手してあるBob_RSA_Pubで検証する。
この検証がtrueならばBobは本物だということがわかる
鍵生成部分のコード
以下のコードは実際のPiping ChatだとexportKey()とgenerateKey()は一塊ではないが、簡素化するために一塊にした。
code:ts
// Bob_Session_ECDH_PubやBob_Encrypt_ECDH_Pubの生成方法
await crypto.subtle.exportKey(
'jwk',
(await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256'},
true,
'deriveKey', 'deriveBits',
)).publicKey,
)
Nipp
勘違いしそうな箇所
デフォルトでは公開鍵認証なしのエンドツーエンド暗号化だけでPiping Chatできるようにしている。
利便性のため。
常に相手のRSA公開鍵が用意する必要があるのは利便性が低下すると思ったから。
公開鍵認証を使っていないときでもセッションIDは作られる。
現在のところ、セッションIDの役目は公開鍵認証するため。そのため、公開鍵認証しないときはセッションIDは作られるだけで、使われない。
経緯ページ
なりすましを防止して、楕円曲線ディフィー・ヘルマン鍵共有でForward secrecyなエンドーツーエンド暗号化する方法を考える
SSHの公開鍵認証のセッションIDの生成方法と、なりすまし防止の話