docs: specify info parameter in SD-APKE pseudocode and protocol steps - #331
docs: specify info parameter in SD-APKE pseudocode and protocol steps#331rocodes wants to merge 1 commit into
Conversation
…. Adjust subsection formatting for easier cross-references.
|
Thanks for drafting this, @rocodes! Substance looks great; I'll suggest some edits next week. |
| - $\text{AKEM}$ as above | ||
| - $\text{KEM}_{PQ} =$ ML-KEM-768 | ||
| - $\text{pskAPKE}$ as above | ||
| - $\text{pskAPKE}$ (HPKE SealAuthPSK), as above |
There was a problem hiding this comment.
I think if we want to unfold the pskAPKE reference like this we need to make it more explicit, e.g.—
| - $\text{pskAPKE}$ (HPKE SealAuthPSK), as above | |
| - $\text{pskAPKE}$, effectively wrapping HPKE's [single-shot `SealAuthPSK()` and `OpenAuthPSK()` APIs][RFC 9180 §6.1] as described above |
—since the function calls and signatures don't line up literally.
There was a problem hiding this comment.
As a side note, I wonder if wanting to offer this shortcut is a sign that it's time to cut out the pskAPKE proof abstraction from the specification entirely and just refer to the HPKE APIs directly. We get the least practical value here out of pskAPKE—in contrast to SD-PKE and SD-APKE, whose concepts and terminology continue to show up elsewhere in documentation and code.
Just a thought—definitely not a task for this pull request.
There was a problem hiding this comment.
💯, I am in favour of removing as many abstraction layers as possible and always have been; I would have supported calling it HPKE.Base with a parenthetical note to the paper instead of sd-pke too if this was acceptable to the group, but for now if you let me know what amount of departure from paper you'll accept I will make whatever change.
My view is we are making a spec for implementors of this protocol, and implementors are expected to use existing crypto libraries, so by this logic I also wondered about removing the DHKEM/AuthEncap explanation or moving it to the glossary, since implementors should use Hpke/DHKEM, not implement HPKE DHKEM.
| | $`(sk_S^{APKE}, pk_S^{APKE}) \gets^{\$} \text{KGen}()`$ | Generate keys | | ||
| | $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info)`$ | Encrypt a message $m$ with associated data $ad$ and $info$ | | ||
| | $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info)`$ | Decrypt a message $m$ with associated data $ad$ and $info$ | | ||
| Additionally, senders and receivers are presumed to possess a $\text{fetch}$ keypair, generated with $\text{Ristretto255.KGen}$, and to have access to the other party's public key portion, $\text{pk}_{fetch}$, as they do with message keys. |
There was a problem hiding this comment.
I think standards-style normative language would be clearer here:
| Additionally, senders and receivers are presumed to possess a $\text{fetch}$ keypair, generated with $\text{Ristretto255.KGen}$, and to have access to the other party's public key portion, $\text{pk}_{fetch}$, as they do with message keys. | |
| Senders and receivers MUST possess a ristretto255 fetching keypair $(sk^{fetch}, pk^{fetch}$ and have access to the other party's public fetching key $pk^{fetch}$, as they do with message keys. |
| | $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information | | ||
| | $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $info$ as part of the additional authenticated information | |
There was a problem hiding this comment.
Notation and consistency:
| | $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information | | |
| | $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $info$ as part of the additional authenticated information | | |
| | $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info\_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information | | |
| | $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info\_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information | |
| m, ad, info_incl=pkR_fetch): # Sender commits to recipient fetch pubkey here | ||
| pkS = sk.public() |
There was a problem hiding this comment.
I think for clarity we have to rebuild pkS from the tuple values:
| m, ad, info_incl=pkR_fetch): # Sender commits to recipient fetch pubkey here | |
| pkS = sk.public() | |
| m, ad, | |
| info_incl=pkR_fetch): # Sender commits to recipient's fetching key | |
| pkS = (skS1.public(), skS2.public()) |
| # pskAEnc `info` param binds all of: c2, 'info' (pkR_fetch), and pkS to encryption context | ||
| info_param = c2 + info + pkS | ||
| (c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c' |
There was a problem hiding this comment.
If I'm following the distinction you're introducing between AuthEnc(info_incl) and pskAEnc(info), I think this would be:
| # pskAEnc `info` param binds all of: c2, 'info' (pkR_fetch), and pkS to encryption context | |
| info_param = c2 + info + pkS | |
| (c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c' | |
| # pskAEnc `info` parameter binds all of: c2, `info_incl` (pkR_fetch), and pkS to encryption context | |
| info_param = c2 + info_incl + pkS | |
| (c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c' |
There was a problem hiding this comment.
And I think we need to make sure to use this same order everywhere else.
| This `info` parameter MUST NOT be transmitted with the ciphertext by the underlying AEAD, since it contains cleartext public keys, which are identifying; comformant implementations of HPKE pass the `info` parameter to the KeySchedule but do not transmit it with the ciphertext. The recipient MUST locally reconstruct the `info` parameter based on the information in the PKE ciphertext and their knowledge of their own $fetch$ key, and supply the reconstructed `info` parameter to successfully decrypt the message. | ||
|
|
||
| _Why this `info` parameter?_ $ Via the `info` parameter, sender binds to the ciphertext authenticated material (ie, not sent inside the AEAD ciphertext and not authenticated via DH-AKEM.AuthEncap): | ||
| The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistent shared secret, `pskAPKE`. |
There was a problem hiding this comment.
If I'm understanding your reference correctly:
| The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistent shared secret, `pskAPKE`. | |
| The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistant public-key encryption scheme, `pskAPKE`. |
| | Sender SD-APKE.MLKEM.pub | Attach to receive replies | Inside SD-PKE ct (unauthenticated) | **commit to in `info`** | Key-swapping | | ||
| | Receiver fetch pubkey | Send to intended recipient | Not transmitted, but DH share used in message hint | **commit to in `info`** | Ciphertext relay/hint swap by impersonator | | ||
| | PSK ciphertext ($`c2`$) | Receiver decaps() to learn PSK | Transmitted in message envelope (unauthenticated) | **commit to it in `info` to bind it to ciphertext** | [Re-encaps attacks](https://durumcrustulum.com/2024/02/24/how-to-hold-kems/#re-encapsulation-attacks) | | ||
| The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity. |
There was a problem hiding this comment.
| The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity. | |
| The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term fetching and SD-PKE public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity. |
| | Receiver fetch pubkey | Send to intended recipient | Not transmitted, but DH share used in message hint | **commit to in `info`** | Ciphertext relay/hint swap by impersonator | | ||
| | PSK ciphertext ($`c2`$) | Receiver decaps() to learn PSK | Transmitted in message envelope (unauthenticated) | **commit to it in `info` to bind it to ciphertext** | [Re-encaps attacks](https://durumcrustulum.com/2024/02/24/how-to-hold-kems/#re-encapsulation-attacks) | | ||
| The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity. | ||
| Despite the name, `pskAPKE` is not a true 'pre-shared' key, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/); the naming convention from [related work](https://eprint.iacr.org/2023/1480) is retained. |
There was a problem hiding this comment.
| Despite the name, `pskAPKE` is not a true 'pre-shared' key, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/); the naming convention from [related work](https://eprint.iacr.org/2023/1480) is retained. | |
| Despite the name, `pskAPKE` is not a true PSK PKE, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/). Our terminology follows Alwen et al. (2023), ["The Pre-Shared Key Modes of HPKE"][alwen-2023]. |
| **Metadata Ciphertext (SD-PKE Ciphertext).** Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]). | ||
| ##### Metadata Ciphertext (SD-PKE Ciphertext) | ||
|
|
||
| Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]). |
There was a problem hiding this comment.
| Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]). | |
| Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's SD-PKE key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]). |
| | | | $`pk_S^{APKE} \gets \text{SD-PKE.Dec}(sk_R^{PKE}, ct^{PKE}, -, -)`$ | | ||
| | | | If $pk_S^{APKE} \neq \bot$: break | | ||
| | | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ | | ||
| | | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ * | |
There was a problem hiding this comment.
A pattern that I like for inline notes (as opposed to footnotes) is to do:
| | | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ * | | |
| | | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ [1] | |
And then after the table or diagram:
**Notes:**
1. $SD-APKE.AuthDec$ reconstructs the `info` parameter used by the sender by concatenating the PQ encapsulated shared secret, decrypted $`pk_S^{APKE}`$, and the receiver's own $`pk_R^{fetch}`$. See [info parameter](#pskaenc-info-parameter)It's a little cleaner than asterisks even if there's just the one note in the list. But up to you.
Towards #246 , docs only:
Spec change:
And clarification changes:
info=info+somethingreferences to avoid confusioninfoparam description, move existing description toAuthEnc/AuthDecsectionAnd formatting: