HPKE利用規格調査メモ
HPKEを利用する上位の規格がどのようにHPKEを使っているのか(動作モード、公開鍵情報の構造や共有方法、INFOやAADをどう使っているか等)を調査する。
目的:
- 自製のHPKEモジュール(hpke-js) へのフィードバック
- HPKEの使い方のベストプラクティス把握
- アプリケーション層での応用アイデア創出・ユースケース抽出
調査対象
- TLS Encrypted Client Hello
- Oblivious DNS Over HTTPS
- Firmware Encryption with SUIT Manifests
- Oblivious HTTP
- The Messaging Layer Security (MLS) Protocol
- Use of HPKE with COSE
調査内容
- 動作モード
- 公開鍵
- データ構造
- 管理方法
- 共有方法 (Recipient -> Sender)
- 共有方法 (Sender -> Recipient)
- 暗号・復号
- INFO (Application-supplied INFOrmation)
- AAD (Additional Authenticated Data)
- bi-directionalの実現方法(あれば)
Encrypted Client Hello (ECH)
TLS の ClientHello も暗号化するやつ。
1. 動作モード
Baseのみ
2. 公開鍵
データ構造
Recipientの公開鍵のデータ構造は下記HpkeKeyConfig。1つの公開鍵にkem_idと受け入れ可能な kdf_idとaead_idのペアのリスト(cipher_suites)が紐づけられている。
opaque HpkePublicKey<1..2^16-1>;
uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke
uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke
uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke
struct {
HpkeKdfId kdf_id;
HpkeAeadId aead_id;
} HpkeSymmetricCipherSuite;
struct {
uint8 config_id;
HpkeKemId kem_id;
HpkePublicKey public_key;
HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
} HpkeKeyConfig;
管理方法
鍵ローテーションに関する言及なし。
共有方法 (Recipient -> Sender)
ECHConfigのリスト(下記ECHConfigList
)を公開する。共有方法自体は Out-of-Scope。一例としては、DNSのHTTPS RR(Service binding and parameter specification via the DNS (DNS SVCB and HTTPS RRs))が挙げられている。
struct {
HpkeKeyConfig key_config;
uint8 maximum_name_length;
opaque public_name<1..255>;
Extension extensions<0..2^16-1>;
} ECHConfigContents;
struct {
uint16 version;
uint16 length;
select (ECHConfig.version) {
case 0xfe0d: ECHConfigContents contents;
}
} ECHConfig;
ECHConfig ECHConfigList<1..2^16-1>;
共有方法 (Sender -> Recipient)
enc
(encapsulated key)は、ECH extensionのペイロードして送る。enc
に加えて、選択したkdf_id/aead_idのペアとconfig_id。config_idは1バイトなのでkem_id(2バイト)を送るより1バイト少なくできている。
enum { outer(0), inner(1) } ECHClientHelloType;
struct {
ECHClientHelloType type;
select (ECHClientHello.type) {
case outer:
HpkeSymmetricCipherSuite cipher_suite;
uint8 config_id;
opaque enc<0..2^16-1>;
opaque payload<1..2^16-1>;
case inner:
Empty;
};
} ECHClientHello;
3. 暗号・復号
INFO (Application-supplied INFOrmation)
固定文字列"tls ech"とシリアライズした上記 ECHConfig。間に1byte(0x00)のセパレータを入れる。
"tls ech" || 0x00 || ECHConfig
AAD (Additional Authenticated Data)
ClientHelloInnerの攻撃者による置き換えを防ぐために、AAD(ClientHelloOuterAAD
)を入れ、ClientHelloOuterを認証する。AADはClientHelloOuterのペイロードを0埋めしてシリアライズしたもの。
final_payload = context.Seal(ClientHelloOuterAAD,
EncodedClientHelloInner)
Oblivious DNS Over HTTPS (ODoH)
DoH (DNS over HTTPS) のクエリ・レスポンスを暗号化する。
1. 動作モード
- Baseのみ
- レスポンスの暗号化に export-only AEAD を用いる
2. 公開鍵
データ構造
ECHと違い、HPKE暗号スイート(kem_id & kdf_id & aead_id)に公開鍵を対応付けている。ECHの設計の方が汎用性が高い気がするが、割り切りとしてはアリか。
struct {
uint16 kem_id;
uint16 kdf_id;
uint16 aead_id;
opaque public_key<1..2^16-1>;
} ObliviousDoHConfigContents;
管理方法
鍵ローテーションに関する言及あり。鍵は毎日更新することを推奨(RECOMMENTED)している。
共有方法 (Recipient -> Sender)
ODoHプロトコルのバージョン情報が入った公開鍵情報リスト(下記ObliviousDoHConfigs
)を共有する。ObliviousDoHConfigs
は推奨順にならべる。共有方法自体は Out-of-Scope。
struct {
uint16 version;
uint16 length;
select (ObliviousDoHConfig.version) {
case 0x0001: ObliviousDoHConfigContents contents;
}
} ObliviousDoHConfig;
ObliviousDoHConfig ObliviousDoHConfigs<1..2^16-1>;
共有方法 (Sender -> Recipient)
Senderのenc
は、暗号化されたクエリの先頭に付与される。
Q_encrypted = enc || ct
暗号化されたクエリ(Q_encrypted
)を運ぶ ODoHのメッセージは以下の構造をとる。
struct {
uint8 message_type;
opaque key_id<0..2^16-1>;
opaque encrypted_message<1..2^16-1>;
} ObliviousDoHMessage;
message_type
はクエリ(0x01)かレスポンス(0x02)か、クエリ時のkey_id
は上記ObliviousDoHConfigContents
を使って以下のように生成される。したがって選択されたHPKE暗号スイートと公開鍵の情報はkey_id
を介してRecipient(サーバ)側に伝わる。
Expand(Extract("", config), "odoh key id", Nh)
なお、レスポンス時のkey_id
には、Recipient側で以下のように生成したresp_nonce
を指定する。
resp_nonce = random(max(Nn, Nk)
3. 暗号・復号
INFO (Application-supplied INFOrmation)
固定文字列のみ。クエリには "odoh query"、レスポンスには "odoh response"。
AAD (Additional Authenticated Data)
クエリの暗号・復号に用いるAADは以下の通り。message_type
とkey_id
の長さ、key_id
自体をコンカテしたものを用いる。
0x01 || len(key_id) || key_id
レスポンスの暗号・復号に用いるAADは以下の通り。message_type
とレスポンスとして受信したresp_nonce
の長さ、resp_nonce
自体をコンカテしたものを用いる。
0x02 || len(resp_nonce) || resp_nonce
bi-directionalの実現方法
レスポンスの暗号・復号方法は以下の通り。
Recipient側でresp_nonce
を以下のように生成し、これをレスポンスのODoHメッセージのkey_id
に指定する。
resp_nonce = random(max(Nn, Nk)
Sender側、Recipient側双方、以下の関数でkeyとnonceを生成する。
def derive_secrets(context, Q_plain, resp_nonce):
secret = context.Export("odoh response", Nk)
salt = Q_plain || len(resp_nonce) || resp_nonce
prk = Extract(salt, secret)
key = Expand(odoh_prk, "odoh key", Nk)
nonce = Expand(odoh_prk, "odoh nonce", Nn)
return key, nonce
Recipient側は以下のように暗号する。
def encrypt_response_body(R_plain, aead_key, aead_nonce, resp_nonce):
aad = 0x02 || len(resp_nonce) || resp_nonce
R_encrypted = Seal(aead_key, aead_nonce, aad, R_plain)
return R_encrypted
Sender側は以下のように復号する。
def decrypt_response_body(context, Q_plain, R_encrypted, resp_nonce):
aead_key, aead_nonce = derive_secrets(context, Q_plain, resp_nonce)
aad = 0x02 || len(resp_nonce) || resp_nonce
R_plain, error = Open(key, nonce, aad, R_encrypted)
return R_plain, error
Firmware Encryption with SUIT Manifests
ファームウェアの暗号化にHPKEをつかう。
IETF SUIT(Software Updates for Internet of Things) WG で仕様策定が進められているIoTデバイスのファームウェア更新規格の1つ。正確にはファームウェア暗号化キーの暗号化にHPKEをつかう。
1. 動作モード
Baseモードのみ
2. 公開鍵
データ構造
COSE (CBOR Object Signing and Encryption)に公開鍵情報を入れる。具体的な仕様は Use of HPKE with COSE で策定中。
管理方法
言及なし。
共有方法 (Recipient -> Sender)
言及なし。
共有方法 (Sender -> Recipient)
Use of HPKE with COSE で規定中。
3. 暗号・復号
INFO (Application-supplied INFOrmation)
言及なし。
AAD (Additional Authenticated Data)
言及なし。
Oblivious HTTP
1. 動作モード
2. 公開鍵
データ構造
管理方法
共有方法 (Recipient -> Sender)
共有方法 (Sender -> Recipient)
3. 暗号・復号
INFO (Application-supplied INFOrmation)
AAD (Additional Authenticated Data)
The Messaging Layer Security (MLS) Protocol
1. 動作モード
2. 公開鍵
データ構造
管理方法
共有方法 (Recipient -> Sender)
共有方法 (Sender -> Recipient)
3. 暗号・復号
INFO (Application-supplied INFOrmation)
AAD (Additional Authenticated Data)
Use of HPKE with COSE
COSE (CBOR Object Signing and Encryption) 上でHPKEを利用するための仕様。COSEは、JWTのベース仕様としておなじみJOSE(JSON Object Signing and Encryption)のCBOR(バイナリ)版。
ここで取り上げている他の仕様と異なり、特定のユースケースにフォーカスしているわけでは無く、COSE上で汎用的にHPKEを利用する方法を規定している。
1. 動作モード
Baseモードのみ。認証はCOSEの枠組みの中で規定されているのでHPKEのものは利用しないとのこと。
2. 公開鍵
データ構造
このドラフトの中で言及されているのは、enc
のフォーマットのみである。COSEのCOSE_Key
構造体を利用する。具体的には、COSEの仕様(RFC9153)の中で、ECDH algorithmのパラメータとしてephemeral keyがCOSE_Key型で定義されており、これを流用するのが現状の方針である。
なお、COSE_KeyはJWKのバイナリ版と考えればよい。RFC9052(Updated RFC8152)に規定されている。
COSE_Key = {
1 => tstr / int, ; kty
? 2 => bstr, ; kid
? 3 => tstr / int, ; alg
? 4 => [+ (tstr / int) ], ; key_ops
? 5 => bstr, ; Base IV
* label => values
}
ephemeral key (-1)の具体例は以下。
/ ephemeral / -1:{
/ kty / 1:2,
/ crv / -1:1,
/ x / -2:h'b2add44368ea6d641f9ca9af308b4079aeb519f11e9b8a55a600b21233e86e68',
/ y / -3:false
},
管理方法
共有方法 (Recipient -> Sender)
特に規定せず。Recipientの公開鍵情報にkidが含まれることは前提としている。
共有方法 (Sender -> Recipient)
COSE_Encrypt0, COSE_Encrypt(定義は以下)で運ぶ。COSE_Encrypt0/COSE_recipient構造のヘッダに、Recipientの公開鍵のkidと生成したencを入れる。
それぞれの定義は、以下の通り(RFC9152より)。
COSE_Encrypt0 = [
Headers,
ciphertext : bstr / nil,
]
COSE_recipient = [
Headers,
ciphertext : bstr / nil,
? recipients : [+COSE_recipient]
]
COSE_Encrypt = [
Headers,
ciphertext : bstr / nil,
recipients : [+COSE_recipient]
]
なお、HPKEのパラメータのうち、kem_id と aead_idは、既存のCOSEパラメータに入れることになっている。kdf_idはどこへ?
- kem_id: ephemeral keyのcrv値に入れる。例えば、
COSE_CRV_HPKE_P256_SHA256
などをIANAレジストリに登録していく模様。 - kdf_id: ???
- aead_id: COSE Headersのalgパラメータ。
COSE_ALG_HPKE_AES_128_GCM
のようなalg値をIANAにいちいち登録していく模様。
3. 暗号・復号
対象外。具体的なユースケース・アプリケーションを想定しているわけではないので。