RFC 9180混合公钥加密的笔记

摘要:本文对IETF的RFC规定的9180混合公钥加密(Hybrid Public Key Encryption,HPKE)进行梳理和研究。

 

关键词:加密,混合加密,数字信封,可鉴别加密,AEAD,秘密共享,密钥协商

 

对标文档:

  1. [RFC 9180] Hybrid Public Key Encryption. 2022-02.

https://www.rfc-editor.org/rfc/rfc9180.html



 

0. 文档思路

混合公钥加密(Hybrid Public Key Encryption,HPKE),即一种标准化的数字信封加密方案,其核心思路如下:

第1步:基于DH秘密协商实现秘密共享/秘密封装DHKEM。见密钥封装/秘密共享KEM

第2步:使用共享秘密生成加解密上下文KeySchedule,包括密钥、Nonce等。见X.X上下文派生KeySchedule<ROLE>

第3步:使用加解密上下文(内含密钥、Nonce),执行可鉴别加密AEAD算法的加密/解密。见5.2 加解密

注:加解密上下文状态需维护,因为内部有个单调递增的计数器需确保每次使用后累加1。

 

 

3. 记号

  • pk<ROLE>和sk<ROLE>:分别表示角色ROLE的公私钥,ROLE可以为发送方S、接收方R、临时密钥对E。
  • I2OSP(n, w):将整数n转为w字节长的大端表示的字节串。
  • Seal(key, nonce, aad, pt):可鉴别加密AEAD的加密算法。key的长度为Nk字节,nonce的长度为Nn字节,生成的MAC长度为Nt字节。
  • Open(key, nonce, aad, ct):可鉴别加密AEAD的解密算法。

 

4. 密码基础部件

4.1 基于Diffie-Hellman协商的密钥封装DHKEM

KDF组件:LabeledExtract + LabeledExpand + ExtractAndExpand

KDFExtract(salt, ikm)

功能:生成长度固定(Nh字节)的伪随机密钥

输入:

  • salt:盐值,任意长度
  • ikm:密钥材料

输出:

  • 伪随机密钥(Nh字节)

例如:为SM3算法,则固定派生Nh = 32字节的密钥。

 

KDFExpand(prk, info, L)

功能:派生任意长度的伪随机密钥

输入:

  • prk:密钥材料
  • info:任意消息串,任意长度
  • L:期望的密钥字节长度

输出:

  • 伪随机密钥(L字节)

例如:为SM2-KDF算法。

 

LabeledExtract(salt, label, ikm)

功能:带标签的固定长度密钥派生,KDF-Extract的进一步封装,以限制/设定一些特定参数

输入:

  • salt:盐值,任意长度
  • label:标签,任意长度
  • ikm:密钥材料

输出:

  • 伪随机密钥(Nh字节)

执行流程见图 4-1

 

LabeledExpand(prk, label, info, L)

功能:带标签的任意长度密钥派生,KDF-Expand的进一步封装,以限制/设定一些特定参数

输入:

  • prk:密钥材料
  • label:标签,任意长度
  • info:任意消息串,任意长度
  • L:期望的密钥字节长度

输出:

  • 伪随机密钥(L字节)

执行流程见图 4-1

 

ExtractAndExpand(dh, kem_context)

功能:专用的秘密派生函数(主要使用函数)

输入:

  • dh:密钥材料,即LabeledExtract的ikm
  • kem_context:任意消息串,即LabeledExpand的info

输出:

  • shared_secret:共享秘密信息(固定为Nsecret字节,例如Nsecret = 32)

执行流程见图 4-1

4-1 shared_secret = ExtractAndExpand(dh, kem_context)

suit_id

suite_id在LabeledExtract、LabeledExpand、ExtractAndExpand中使用,为

suite_id = "KEM" || I2OSP(kem_id, 2))

其中,I2OSP(kem_id, 2)表示把7.1节表2的kem_id转为2字节长的字节串。

密钥封装/秘密共享KEM

密钥封装方案的目的:共享一个秘密值,基于临时公私钥对、对方公钥,可选地基于己方私钥(AuthEncap+AuthDecap),其核心思想是DH方案:

[skE]pkR = [skE・skR]G = [skR]pkE

[skS]pkR = [skS・skR]G = [skR]pkS

其中:pk<ROLE>和sk<ROLE>分别表示角色ROLE的公私钥,ROLE可以为发送方S、接收方R、临时密钥对E。

 

有两种方案:

方案

执行

特点

不使用发送方公私钥的方案

发送方Encap

接收方Decap

无法提供发送方真实性

使用发送方公私钥的方案

发送方AuthEncap

接收方AuthDecap

提供发送方真实性

Encap不使用发送方私钥的秘密封装

shared_secret, enc = Encap(pkR)

功能:秘密封装(不使用发送方私钥)

输入:

  • pkR:接收方公钥

输出:

  • shared_secret:共享的秘密信息,长度固定为Nsecret字节。
  • enc:临时公钥的编码数据,长度为Nenc字节。

步骤:如下图。

图 shared_secret, enc = Encap(pkR)

注:文中提到GenerateKeyPair可以使用DeriveKeyPair(random(Nsk))的方式实现,后者的实现可见7.1.3节。个人认为可以自由发挥。

Decap不使用发送方公钥的秘密解封

shared_secret = Decap(enc, skR, pkR)

功能:秘密解封(不使用发送方公钥)

输入:

  • enc:临时公钥的编码数据(发送方提供)
  • skR:接收方私钥
  • pkR:接收方公钥

输出:

  • shared_secret:共享的秘密信息

步骤:如下图。

图 shared_secret = Decap(enc, skR, pkR)

AuthEncap使用发送方私钥的秘密封装

shared_secret, enc = AuthEncap(pkR, skS)

功能:秘密封装(使用发送方私钥)

输入:

  • pkR:接收方公钥
  • skS:发送方私钥

输出:

  • shared_secret:共享的秘密信息
  • enc:临时公钥的编码数据

步骤:如下图。

图 shared_secret, enc = AuthEncap(pkR, skS)

AuthDecap使用发送方公钥的秘密解封

shared_secret =AuthDecap(enc, skR, pkR, pkS)

功能:秘密解封(使用发送方公钥)

输入:

  • enc:临时公钥的编码数据(发送方提供)
  • skR:接收方私钥
  • pkR:接收方公钥
  • pkS:发送方公钥

输出:

  • shared_secret:共享的秘密信息

步骤:如下图。

图 shared_secret = AuthDecap(enc, skR, pkR, pkS)

 

5. 混合公钥加密方案

5.1 创建加解密的上下文Setup

创建加解密的上下文为Setup<MODE><ROLE>,其中

  • <MODE>:包括如下四种模式(default_psk = ""default_psk_id = ""

MODE

使用己方私钥

使用预共享密钥

秘密共享方案

上下文派生方案

Base

5.1.1

×

×

Encap + Decap

KeySchedule

default_pskdefault_psk_id

PSK

5.1.2

×

Encap + Decap

KeySchedule

pskpsk_id

Auth

5.1.3

×

AuthEncap + AuthDecap

KeySchedule

default_pskdefault_psk_id

AuthPSK

5.1.4

AuthEncap + AuthDecap

KeySchedule

pskpsk_id

  • <ROLE>:发送方S或接收方R

注意:

  1. KeySchedule创建生成的上下文仅用于单向传输——发送方仅用它加密,接收方仅用它解密。如果要双向传输,可以使用5.3节的秘密导出/派生方案得到另一个方向传输加密AEADkeynonce

X.X上下文派生KeySchedule<ROLE>

本节内容见文献的5.1节(至5.1.1节前)的内容,Page14

Context<ROLE>(key, base_nonce, seq = 0, exporter_secret) = KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id)

输入:

  • mode:密钥派生模式,为四种之一,mode_base(0x00)、mode_psk(0x01)、mode_auth(0x02)、mode_auth_psk(0x03)
  • shared_secret:DHKEM建立的共享秘密值
  • info:应用层提供的信息(可选,默认为空)
  • psk:预共享密钥(可选,默认为空)
  • psk_id:预共享密钥的ID(可选,默认为空)

输出:

  • key:可鉴别加密AEAD的密钥
  • base_nonce可鉴别加密AEAD的nonce基线值(用此值和计数器生成AEAD的nonce)
  • seq:序列号,被初始化为零
  • exporter_secret:仅用于秘密导出/派生的信息(见5.3节)

步骤:如下图。

Context<ROLE>(key, base_nonce, seq = 0, exporter_secret) =

KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id)

5.1.1—5.1.4 具体方案

5.1.15.1.4的创建加解密上下文方案

(表中default_psk = ""default_psk_id = ""

 

发送方

接收方

5.1.1. Encryption to a Public Key

SetupBaseS(pkR, info):

  1. shared_secret, enc = Encap(pkR)
  2. return enc,
    KeyScheduleS(
    mode_base, shared_secret, info,
    default_psk, default_psk_id)

SetupBaseR(enc, skR, info):

  1. shared_secret = Decap(enc, skR)
  2. return
    KeyScheduleR(mode_base, shared_secret, info,
    default_psk, default_psk_id)

5.1.2. Authentication Using a Pre-Shared Key

SetupPSKS(pkR, info, psk, psk_id):

  1. shared_secret, enc = Encap(pkR)
  2. return enc,
    KeyScheduleS(
    mode_psk, shared_secret, info,
    psk, psk_id)

SetupPSKR(enc, skR, info, psk, psk_id):

  1. shared_secret = Decap(enc, skR)
  2. return
    KeyScheduleR(mode_psk, shared_secret, info,
    psk, psk_id)

5.1.3. Authentication Using an Asymmetric Key

SetupAuthS(pkR, info, skS):

  1. shared_secret, enc = AuthEncap(pkR, skS)
  2. return enc,
    KeyScheduleS(
    mode_auth, shared_secret, info,
    default_psk, default_psk_id)

SetupAuthR(enc, skR, info, pkS):

  1. shared_secret = AuthDecap(enc, skR, pkS)
  2. return
    KeyScheduleR(mode_auth, shared_secret, info,
    default_psk, default_psk_id)

5.1.4. Authentication Using Both a PSK and an Asymmetric Key

SetupAuthPSKS(pkR, info, psk, psk_id, skS):

  1. shared_secret, enc = AuthEncap(pkR, skS)
  2. return enc, KeyScheduleS(mode_auth_psk, shared_secret, info,
    psk, psk_id)

SetupAuthPSKR(enc, skR, info, psk, psk_id, pkS):

  1. shared_secret = AuthDecap(enc, skR, pkS)
  2. return
    KeyScheduleR(mode_auth_psk, shared_secret, info,
    psk, psk_id)

 

 

 

5.2 加解密

加密 ct = ContextS.Seal(aad, pt)

  1. 执行AEAD加密,密钥和nonce在上下文中(ComputeNonce是计算AEAD用的nonce):

ct = Seal(self.key, self.ComputeNonce(self.seq), aad, pt)

  1. 维护状态,让计数器累加1self.IncrementSeq()
  2. 返回密文return ct

 

解密pt = ContextR.Open(aad, ct)

  1. 执行AEAD的解密,密钥和nonce在上下文中(ComputeNonce是计算AEAD用的nonce):

pt = Open(self.key, self.ComputeNonce(self.seq), aad, ct)

  1. 核验明文正确性,if pt == OpenError: raise OpenError
  2. 维护状态,让计数器累加1self.IncrementSeq()
  1. 返回明文return pt

 

计算AEADnoncenonce = Context<ROLE>.ComputeNonce(seq)

  1. 计数器seq转字节串,seq_bytes = I2OSP(seq, Nn)
  2. 输出nonce基线值与seq_bytes异或的结果,return xor(self.base_nonce, seq_bytes)

 

计数器累加1 Context<ROLE>.IncrementSeq()

  1. 判断计数器是否溢出,if self.seq >= (1 << (8*Nn)) - 1: raise MessageLimitReachedError
  2. 计数器累加1self.seq += 1

 

5.3 秘密导出/派生

Context.Export(exporter_context, L)

  1. return LabeledExpand(self.exporter_secret, "sec", exporter_context, L)

 

5.1节建立的上下文是单向的,要建立双向的,可以使用本节的秘密导出/派生方案生成另一个上下文的密钥和nonce

key = context.Export("response key", Nk)

nonce = context.Export("response nonce", Nn)

 

6. 一步式API

6.1 一步式加密 / 一步式解密

一步式加密Seal<MODE>(…) /一步式解密Open<MODE>(...)

1步:执行enc, ctx = Setup<MODE><ROLE>,其中<MODE>5.1描述的四种模式,<ROLE>为发送方S或接收方R

2步:执行加密ct = ctx.Seal(aad, pt)或解密pt= ctx.Open(aad, ct),其中SealOpen分别为可鉴别式加密AEAD的加密算法和解密算法。

3步:返回密文 / 明文。

6.2 一步式派生

发送方 / 接收方一步式派生SendExport<MODE>(...) / ReceiveExport<MODE>(...)

1步:执行enc, ctx = Setup<MODE><ROLE>,其中<MODE>5.1描述的四种模式,<ROLE>为发送方S或接收方R

2步:执行exported = ctx.Export(exporter_context, L)

3步:返回enc, exported

 

9. 安全性

  1. 如果使用量子安全的KEM(使用抗量子密码算法),HPKE的认证模式(Auth模式)可以提供量子安全性。
  2. HPKE可以实现IND-CCA2安全性。
  3. 隐藏明文长度:HPKE不会隐藏明文长度,应用层需要实现额外的填充机制。
  4. 双向加密:HPKE支持单向加密,双向加密需要应用层实现。

 

附录A. 测试向量

见原文,略。

posted @ 2025-10-28 09:20  艾米的爸爸  阅读(57)  评论(0)    收藏  举报  来源