HPKE(Hybrid Public Key Encryption)の設計
ECH や MLS が共通の暗号土台に HPKE を選んだ理由が腑に落ちる。KEM・KDF・AEAD を組み合わせる構造、Base/PSK/Auth など複数モード、誤用しにくい API 設計の狙いまで原理から押さえられる。
- 1.HPKE(RFC 9180)は KEM × KDF × AEAD の三部品を識別子で選ぶ公開鍵暗号フレームワーク。送信側で一度カプセル化した鍵から鍵スケジュールで context を導出し、その後は任意個のメッセージを AEAD で seal/open する。
- 2.モードは Base/PSK/Auth/Auth+PSK の四種。共有値に加えて事前共有鍵(PSK)や送信者の静的鍵を鍵スケジュールへ混ぜ、それぞれ匿名・PSK 認証・送信者認証を与える。混ぜ方は KDF の info/secret に集約され安全に合成される。
- 3.context が nonce をシーケンス番号から自動採番し AEAD 鍵を内部に隠すため、利用者が nonce を渡せない。この誤用しにくい API 設計のおかげで ECH(TLS の SNI 暗号化)や MLS が安全に HPKE を再利用できる。
HPKE が解く問題:ハイブリッド暗号を「誤用しにくい標準部品」にする
公開鍵で対称鍵を運び、データ本体は AEAD で守る——ハイブリッド暗号と KEM-DEM の原理自体は古くから知られていました。ところが ECIES のように曲線・KDF・AEAD の組み合わせが標準ごとにバラバラで、相互運用が効かず、nonce や鍵導出の扱いを各実装が独自に決めるため事故も起きやすい。HPKE(Hybrid Public Key Encryption, RFC 9180) はこの状況を、KEM × KDF × AEAD の三部品を識別子で指定する一つのフレームワークに統一し、しかも利用者が間違えにくい API を備えた標準として整理したものです。
HPKE の眼目は二つあります。第一に、ハイブリッド暗号の構造を再利用可能な共通基盤として固め、ECH・MLS・OHTTP など複数のプロトコルが同じ土台を共有できるようにしたこと。第二に、後述する context という抽象で nonce や AEAD 鍵を内部に隠し、利用者が触れる API を seal/open だけに絞ったことです。
HPKE の暗号スイートは三つの識別子の組で決まります。
(1) KEM:対称鍵のカプセル化。DHKEM(X25519, HKDF-SHA256)、DHKEM(P-256, HKDF-SHA256)、PQC なら X-Wing や ML-KEM 系。
(2) KDF:共有値から鍵を導出する関数。HKDF-SHA256/HKDF-SHA512 など。
(3) AEAD:データの暗号化・認証(DEM 役)。AES-128-GCM、AES-256-GCM、ChaCha20-Poly1305。
KEM 部だけを差し替えればポスト量子暗号へ移行でき、データ層には手を入れずに済みます。
全体の流れ:カプセル化 → 鍵スケジュール → context
HPKE の処理は「鍵を一度封じる」段階と「その鍵から作った context で何度も暗号化する」段階に分かれます。送信側 SetupBaseS と受信側 SetupBaseR が対になります。
送信側 SetupBaseS(pkR, info):
(shared_secret, enc) = KEM.Encap(pkR) # 共有値とカプセル enc を同時に得る
context = KeySchedule(mode_base, shared_secret, info, psk="", psk_id="")
return enc, context # enc は受信側へ平文で送る
受信側 SetupBaseR(enc, skR, info):
shared_secret = KEM.Decap(enc, skR) # 同じ共有値を復元
context = KeySchedule(mode_base, shared_secret, info, psk="", psk_id="")
return context # まったく同じ context を得る
KEM.Encap が共有値 shared_secret とカプセル enc を同時に返すのは KEM-DEM の核心そのものです(詳細はハイブリッド暗号を参照)。HPKE 特有なのは、共有値をそのまま AEAD 鍵にせず、必ず 鍵スケジュール(KeySchedule) を通して context を導出する点です。info は呼び出し側が与える任意の文脈文字列で、用途ごとに鍵を分離するドメイン分離(domain separation) に使います(例 ECH なら ECH 用、MLS なら別用途)。同じ鍵ペアでも info が違えば導出される鍵が変わり、用途をまたいだ鍵の混線を防げます。
導出された context が以後の主役です。context は AEAD 鍵 key、ベース nonce base_nonce、エクスポート用秘密 exporter_secret、そしてシーケンス番号 seq を内部に保持します。
context が内部に持つ状態:
key # AEAD の鍵(利用者からは見えない)
base_nonce # nonce の基底値
seq = 0 # メッセージごとに +1 されるカウンタ
exporter_secret # 別鍵を派生するための種
context.Seal(aad, pt): # 送信側
nonce = base_nonce XOR (seq を big-endian で表したもの)
ct = AEAD.Seal(key, nonce, aad, pt)
seq = seq + 1 # 自動で進む
return ct
ここが HPKE の API 設計の肝です。nonce は base_nonce XOR seq で自動採番され、利用者は nonce を渡せない。AEAD の安全性は同一鍵・同一 nonce を二度使わないことに全面依存し、nonce 再利用は平文の XOR 露出どころか認証鍵の復元による任意偽造を招きます。HPKE は seq を内部カウンタで単調増加させ、送受信で同じ順序を保つことで、利用者がどう使っても nonce 再利用が起きないよう構造で封じています。open 側も同じ seq を進めるため、メッセージは順序どおりに復号する前提になります。
四つのモード:何を鍵スケジュールに混ぜるか
HPKE は単に匿名で鍵を封じるだけでなく、送信者の認証や事前共有鍵を織り込めます。これがモードで、KEM 共有値に加えて何を鍵スケジュールへ混ぜるかの違いに集約されます。
| モード | 混ぜる材料 | 得られる性質 | 対応する Setup |
|---|---|---|---|
| Base | KEM 共有値のみ | 受信者だけが復号できる(送信者は匿名) | SetupBaseS/R |
| PSK | 共有値 + 事前共有鍵 PSK | PSK を知る者だけが送信者と確認できる(対称鍵認証) | SetupPSKS/R |
| Auth | 共有値 + 送信者の静的鍵 | 受信者が送信者の公開鍵で送信者を認証できる | SetupAuthS/R |
| Auth+PSK | 共有値 + 静的鍵 + PSK | 送信者認証と PSK 認証の両方 | SetupAuthPSKS/R |
Auth モード は KEM を AuthEncap/AuthDecap という認証付き版に切り替えます。送信者が自分の静的秘密鍵 skS を共有値の計算へ持ち込むため、受信者は送信者の静的公開鍵 pkS を使ってはじめて同じ共有値に到達できます。つまり「この共有値に至れたのは skS を持つ者だけ」という形で送信者認証が成立します。DHKEM では具体的に、エフェメラル鍵と受信者鍵の DH に加えて、送信者静的鍵と受信者鍵の DH を連結して鍵導出に流し込みます。
DHKEM の AuthEncap(pkR, skS) 概念:
(skE, pkE) = GenerateKeyPair() # エフェメラル鍵
dh = DH(skE, pkR) || DH(skS, pkR) # 二つの DH を連結
enc = pkE
shared_secret = ExtractAndExpand(dh, pkE || pkR || pkS)
return shared_secret, enc
PSK モード は、KEM とは別経路で共有した秘密 psk を鍵スケジュールの抽出段に混ぜます。psk を知る当事者だけが正しい context を作れるので、対称鍵に基づく送信者認証になります。重要なのは、これらの材料がすべて KDF の入力(secret や info)に集約され、合成の安全性が KDF の性質として保証される点です。モードを識別する mode バイトと、PSK の識別子なども info に織り込まれるため、Base のつもりの context と Auth の context が偶然一致することはありません。
Auth モードの送信者認証は DH に基づく暗黙認証(deniable authentication) であり、デジタル署名のような第三者検証可能性や否認不可性は持ちません。受信者は「skS を持つ送信者からだ」と確信できますが、その事実を第三者に証明することはできない(受信者自身も同じタグを作れるため)。公開検証や否認不可が必要なら署名スキームを別途併用します。HPKE の Auth は秘匿通信路の中での相互確認向けです。
エクスポート機能と Secret Export only の使い方
HPKE は暗号文を直接やり取りするだけの道具ではありません。context.Export(exporter_context, L) は exporter_secret から 任意用途の対称鍵を L バイト派生します。これは「HPKE で鍵共有だけ済ませ、得た鍵を呼び出し側プロトコルが自由に使う」ための口で、seal/open を一切使わずエクスポートだけ行う Secret Export only の運用も正式に想定されています。
context.Export("mls confirmation", 32) # 確認用の 32 バイト鍵
context.Export("application traffic", 32) # 別用途の鍵
exporter_context ラベルが違えば独立した鍵が出るため、一回のカプセル化から複数の独立鍵を安全に切り出せます。MLS が HPKE をこの形で使うのは、まさにこのエクスポートが「鍵合意の出力」として綺麗に取り出せるからです。
実プロトコルでの利用:ECH と MLS
HPKE の設計が活きるのが、共通土台として複数プロトコルに組み込まれている点です。
ECH(Encrypted Client Hello) は、TLS 1.3 のハンドシェイク冒頭で平文のまま漏れていた SNI(接続先ホスト名) などを暗号化する仕組みです。クライアントは DNS の HTTPS レコードで配布された ECH 設定からサーバーの HPKE 公開鍵を入手し、SetupBaseS で内側 ClientHello を seal して外側 ClientHello に同梱します。Base モードを使うのはクライアントが匿名でよいからで、enc(カプセル)を外側に載せ、サーバーは秘密鍵で open して本来の接続先を知ります。ここで HPKE の nonce 自動管理が効き、ハンドシェイク実装が nonce を誤る余地がありません。これはTLS のレコード層が握手後の通信を守るのとは別に、握手の入口のメタデータを隠す層です。
MLS(Messaging Layer Security) は大規模グループの暗号化メッセージングプロトコルで、メンバーへ新しいグループ秘密を配る HPKEPublicKey への封入 に HPKE を使います。各メンバーのリーフ公開鍵宛てに HPKE で鍵を seal し、ツリー構造で効率的に配布する。ここでは前述のエクスポートや Auth モードが、グループ鍵の更新(前方秘匿性とラチェットの文脈)を支えます。
ほとんどのプロトコルは「一つの平文を一回だけ封じる」ため、HPKE は Seal(pkR, info, aad, pt) という単発 API を用意しています。内部で SetupBaseS → 一度だけ context.Seal → context 破棄、をまとめて行い、enc || ct を返す。ECH の内側 ClientHello のように一発で済む用途はこれで十分で、context の寿命管理を呼び出し側が誤る隙すら消せます。複数メッセージを順に送るストリーミング用途のときだけ、明示的に Setup して context を保持します。
誤用しにくい API 設計という思想
HPKE 全体を貫くのは「正しく使うのが最も簡単で、危険な使い方を構造的に不可能にする」という設計哲学です。三点で具体化されています。第一に、AEAD 鍵と nonce を context の内部に隠し、利用者が触れるのは seal/open/export だけ——AEAD の最大の事故源である nonceを API から消しました。第二に、info と exporter_context による徹底したドメイン分離で、用途やモードをまたいだ鍵の取り違えを防ぎます。第三に、鍵共有・モード認証・鍵導出がすべてHKDF の Extract/Expandに集約され、合成の安全性が一点に証明可能な形で帰着します。
公開鍵で対称鍵を運ぶ独自実装を書こうとしているなら、まず HPKE で代替できないか検討すべきです。曲線・KDF・AEAD の組み合わせは標準スイートから選ぶだけで済み、nonce 管理という最大の事故源が API から消える。送信者認証が要るなら Auth モード、別経路の事前共有鍵があるなら PSK モード、鍵合意だけ欲しいなら Secret Export only、と用途がモードへ素直に写ります。認証付き鍵交換のように双方向の対話が必要な場面とは異なり、HPKE は「受信者の公開鍵さえ知っていれば一方向に安全な暗号文を送れる」非対話な秘匿に最適化されています。
まとめ
HPKE(RFC 9180)は、ハイブリッド暗号を KEM × KDF × AEAD の三部品からなる標準フレームワークへ整理し、誤用しにくい API を被せたものです。送信側は KEM で共有値とカプセル enc を同時に得て、鍵スケジュールで context を導出する。context は AEAD 鍵と nonce を内部に隠し、シーケンス番号から nonce を自動採番することで、利用者がどう使っても nonce 再利用が起きない構造になっています。
モードは Base/PSK/Auth/Auth+PSK の四種で、KEM 共有値に事前共有鍵や送信者静的鍵を混ぜる材料の違いとして鍵スケジュールへ集約され、匿名・PSK 認証・送信者認証を与えます。info と exporter_context によるドメイン分離、export による鍵派生、単発 API といった仕組みが揃い、ECH の SNI 暗号化や MLS のグループ鍵配布という別々のプロトコルが同じ土台を安全に再利用できる。KEM 部だけ差し替えればポスト量子暗号へ移行できる拡張性も含め、HPKE は「公開鍵暗号を正しく使うための共通部品」を体現しています。
セキュリティ Article
HPKE(Hybrid Public Key Encryption)の設計を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
HPKE
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 6
導入後に効く点
モードは Base/PSK/Auth/Auth+PSK の四種。共有値に加えて事前共有鍵(PSK)や送信者の静的鍵を鍵スケジュールへ混ぜ、それぞれ匿名・PSK 認証・送信者認証を与える。混ぜ方は KDF の info/secret に集約され安全に合成される。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「HPKE / KEM」に近いか確認する。
- 強みである「HPKE(RFC 9180)は KEM × KDF × AEAD の三部品を識別子で選ぶ公開鍵暗号フレームワーク。送信側で一度カプセル化した鍵から鍵スケジュールで context を導出し、その後は任意個のメッセージを AEAD で seal/open する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。