C#で楕円曲線暗号を使いたい③

ECDHからWireGuardと
ECIESの正体が見えてきた

前回は、ECDHを使ってServerとClientが秘密鍵そのものを交換せずに、同じ共有秘密を作れることを確認しました。さらに、その共有秘密をHKDFで鍵に変換し、AES-GCMで暗号化・復号するところまで、自分で組み立てて試しました。

ここまで理解してくると、最初は別の話に見えていたものが、だんだん一本につながってきます。
たとえばWireGuardの設定ファイルです。普段は「そう書くもの」として見ていた PrivateKey と PublicKey が、ECDHを理解したあとでは、急に意味のあるものとして見えてきます。

そしてもう一つ、最初に使おうとして失敗したECIESも、単なる「使えなかったAPI」ではなく、ECDH・KDF・共通鍵暗号の組み合わせとして理解できるようになってきます。

シリーズの流れ
このシリーズ全体の流れ。①でECIESにぶつかり、②でECDHを自分で組み立て、③でWireGuardとECIESの中にある共通構造を回収する。

②で見えた「自分の秘密鍵+相手の公開鍵」という構造

②でやったことを一言で書くと、

自分の秘密鍵
+
相手の公開鍵
↓
共有秘密

です。

Server側では「自分の秘密鍵 s」と「相手の公開鍵 C = cG」を使って sC を計算し、Client側では「自分の秘密鍵 c」と「相手の公開鍵 S = sG」を使って cS を計算しました。結果として、両者は同じ共有秘密に到達できます。

最初は数学っぽい話に見えていたのですが、この構造を頭に入れてから別の技術を見ると、「あ、これも同じ考え方だ」と気づける場面が出てきます。

これ、wg0.confじゃないか

その代表がWireGuardです。
WireGuardの設定ファイル wg0.conf には、自分については PrivateKey を書き、相手については PublicKey を書きます。

普段は設定項目として何気なく見ているだけですが、ECDHを知ったあとで見ると、これはかなり意味深です。
なぜなら、②で見た構造そのものが、そこに現れているからです。

INI
[Interface]
PrivateKey = ...

[Peer]
PublicKey = ...

つまり、概念的にはこう見えます。

  • 自分の PrivateKey = 自分の秘密鍵
  • 相手の PublicKey = 相手の公開鍵

この時点で、「WireGuardも楕円曲線Diffie-Hellmanの考え方の上に立っているのでは?」という見方ができます。

WireGuardのPrivateKeyとPublicKeyの関係
WireGuardの設定にも、「自分の秘密鍵」と「相手の公開鍵」という、ECDHで見た構造が現れています。

この図が示しているのは、「WireGuard = 単なる設定ファイル」ではなく、鍵交換の考え方が設定項目のレベルにまで落ちてきているということです。

もちろん、WireGuardがやっていることは、単純な1回のECDHだけではありません。けれども、少なくとも「自分の秘密鍵」と「相手の公開鍵」を組み合わせる、という基本発想は共通しています。

ただし、WireGuardは単純なECDHそのものではない

ここで注意したいのは、
WireGuard = ②で書いた単純なECDHコードそのもの
ではない、ということです。

②で試したのは、学習用としてかなり最小限の構成でした。静的な秘密鍵と相手の公開鍵から共有秘密を1回作り、その共有秘密をHKDFに通し、AES-GCMで暗号化する――という流れです。

一方でWireGuardは、実際のVPNとして動くために、もっと多くの要素を持っています。相手の認証、通信ごとのセッション鍵、鍵の更新、ハンドシェイクなど、実運用に必要な仕組みが組み込まれています。

単純ECDHとWireGuardの違い
単純ECDHは学習用の最小構成。WireGuardは同じ原理を使いながら、一時鍵やハンドシェイクを組み合わせて実運用向けのセッション鍵を作る。

この図で大事なのは、「違う技術」というより、同じ原理がより実用的な形に発展しているという見方です。

単純ECDHでは、

自分の秘密鍵
+
相手の公開鍵
↓
共有秘密

ですが、WireGuardではここにさらに一時鍵やハンドシェイクが加わります。つまり、楕円曲線Diffie-Hellmanの原理をベースにしながら、そのままでは足りない実務上の要求を満たすように強化されているわけです。

共通点
・どちらも楕円曲線Diffie-Hellmanの原理を使う
・「自分の秘密鍵」と「相手の公開鍵」が重要
・最終的には通信に使う共通の鍵を作る
相違点
・単純ECDHは学習用の最小構成
・WireGuardは一時鍵・ハンドシェイク・鍵更新などを含む実運用向けプロトコル
・②ではAES-GCMを使ったが、WireGuardではChaCha20-Poly1305など
 別の部品が組み合わされる

ここで、最初のECIESに戻る

ここまで来たところで、いよいよ最初の話に戻れます。
このシリーズの出発点は、

ECCで、RSAのように「相手の公開鍵で暗号化」したい

というものでした。

そこで見つけたのがECIESです。
①の時点では、「ECC版のRSAみたいなものかな」という理解に近かったのですが、②でECDHを理解したあとに見ると、その中身がかなり違って見えてきます。

ECIESは、楕円曲線で平文そのものを直接暗号化する、というよりは、

  • 一時鍵を作る
  • ECDHで共有秘密を作る
  • KDFで暗号鍵を導出する
  • 共通鍵暗号で実際のデータを暗号化する

というハイブリッド構成として見る方が実態に近いのです。

ECIESの全体像
ECIESは見た目としては「公開鍵で暗号化」に見えるが、内部では一時鍵・ECDH・KDF・共通鍵暗号が組み合わされている。

この図を②の知識を使って読むと、かなり分かりやすくなります。

送信者は、受信者の公開鍵 C = cG をあらかじめ知っています。そこで、自分で一時秘密鍵 e を作り、一時公開鍵 E = eG を生成します。そして受信者の公開鍵 C と、自分の一時秘密鍵 e を使って共有秘密を計算します。

一方、受信者は、自分の秘密鍵 c と、送られてきた一時公開鍵 E を使って、同じ共有秘密を再現できます。

つまり、

  • 送信者側:eC = ecG
  • 受信者側:cE = ceG

であり、どちらも同じ共有秘密に到達できます。

この時点で、①のときにソースコードの中で見かけた ECDHBasicAgreement の意味がはっきりしてきます。
ECIESの内部で共有秘密を作るために、まさにECDHが使われていたわけです。

つまり①では、「ECIESを使おうとしていたら、なぜかECDHが出てきた」と感じていたものが、②を通ったあとでは「出てきて当然」と理解できるようになります。

※ ここで図中の共通鍵暗号として AES-GCM を使っているのは、②で扱った構成に合わせて理解しやすく整理しているためです。
ECIES一般には複数の構成や実装があり、必ずしもすべてが同じ暗号部品を使うわけではありません。

ECIESはRSAと同じように見えて、中身はかなり違う

利用者の視点から見ると、ECIESはかなりRSAっぽく見えます。
受信者の公開鍵を知っていれば送信者は暗号化でき、受信者の秘密鍵を持っている本人だけが復号できる。見た目だけなら、たしかに「公開鍵暗号」です。

しかし内部を見ると、処理の流れはかなり違います。
RSAは公開鍵暗号アルゴリズムそのもので暗号化・復号を行う印象が強いですが、ECIESでは、ECDHで共有秘密を作り、その共有秘密から導出した鍵で実データを暗号化するという構成になります。

だから、見た目は似ていても、中身は「ECC版のRSA」というより、一時ECDH + KDF + 共通鍵暗号 と見る方がしっくりきます。

項目RSA的な見え方ECIESの内部
暗号化相手の公開鍵を使う相手の公開鍵 + 送信者の一時秘密鍵
復号自分の秘密鍵を使う自分の秘密鍵 + 送信者の一時公開鍵
本文の保護公開鍵暗号で処理する印象共有秘密から導いた共通鍵で保護
鍵導出あまり前面に出ないKDFが重要な役割を持つ
全体像単体の公開鍵暗号ハイブリッド暗号

①のNotImplementedExceptionも、違って見えてくる

①では、

CipherUtilities.GetCipher("ECIES")

までは進んだものの、その後の Init で NotImplementedException が発生しました。

最初はただの行き止まりに見えました。
しかしここまで来ると、それは「楕円曲線暗号では公開鍵暗号ができない」という意味ではなく、BouncyCastle C#版のそのAPI経路が未実装だったというだけだと分かります。

暗号方式としてのECIESそのものが成立しないのではなく、その高レベルAPIの実装が揃っていなかった、という話です。

むしろ、その例外にぶつかったおかげで、内部の仕組みをここまで追うことになりました。
もし最初から何も考えずに一発で動いていたら、ECDHBasicAgreement の意味も、WireGuardとのつながりも、ここまで意識しなかったかもしれません。

そういう意味では、あの例外は単なる失敗ではなく、このシリーズ全体の入口だったとも言えます。

まとめ

この3回で、最初に持っていた

ECCでRSAのようなことをしたい

という疑問に対して、自分なりの整理ができました。

①では、BouncyCastleでECIESを使おうとして NotImplementedException にぶつかりました。
②では、その途中で見つけたECDHを自分で組み立て、共有秘密・HKDF・AES-GCMまで確かめました。
③では、その理解を使ってWireGuardとECIESを見直し、どちらの中にも「自分の秘密鍵 + 相手の公開鍵」という楕円曲線Diffie-Hellmanの考え方が流れていることが見えてきました。

最初の目的は、「C#でECIESを使いたい」だけでした。
結局、APIを一つ呼んで終わるより、ずいぶん遠回りしました。
けれども、その遠回りのおかげで、

楕円曲線
→ ECDH
→ KDF
→ 共通鍵暗号
→ WireGuard
→ ECIES

が一本につながりました。
こういう寄り道も、たぶん悪くありません。

返信を残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA