--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package hpke
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "errors"
+ "fmt"
+
+ "golang.org/x/crypto/chacha20poly1305"
+)
+
+// The AEAD is one of the three components of an HPKE ciphersuite, implementing
+// symmetric encryption.
+type AEAD interface {
+ ID() uint16
+ keySize() int
+ nonceSize() int
+ aead(key []byte) (cipher.AEAD, error)
+}
+
+// NewAEAD returns the AEAD implementation for the given AEAD ID.
+//
+// Applications are encouraged to use specific implementations like [AES128GCM]
+// or [ChaCha20Poly1305] instead, unless runtime agility is required.
+func NewAEAD(id uint16) (AEAD, error) {
+ switch id {
+ case 0x0001: // AES-128-GCM
+ return AES128GCM(), nil
+ case 0x0002: // AES-256-GCM
+ return AES256GCM(), nil
+ case 0x0003: // ChaCha20Poly1305
+ return ChaCha20Poly1305(), nil
+ case 0xFFFF: // Export-only
+ return ExportOnly(), nil
+ default:
+ return nil, fmt.Errorf("unsupported AEAD %04x", id)
+ }
+}
+
+// AES128GCM returns an AES-128-GCM AEAD implementation.
+func AES128GCM() AEAD { return aes128GCM }
+
+// AES256GCM returns an AES-256-GCM AEAD implementation.
+func AES256GCM() AEAD { return aes256GCM }
+
+// ChaCha20Poly1305 returns a ChaCha20Poly1305 AEAD implementation.
+func ChaCha20Poly1305() AEAD { return chacha20poly1305AEAD }
+
+// ExportOnly returns a placeholder AEAD implementation that cannot encrypt or
+// decrypt, but only export secrets with [Sender.Export] or [Recipient.Export].
+//
+// When this is used, [Sender.Seal] and [Recipient.Open] return errors.
+func ExportOnly() AEAD { return exportOnlyAEAD{} }
+
+type aead struct {
+ nK int
+ nN int
+ new func([]byte) (cipher.AEAD, error)
+ id uint16
+}
+
+var aes128GCM = &aead{
+ nK: 128 / 8,
+ nN: 96 / 8,
+ new: newAESGCM,
+ id: 0x0001,
+}
+
+var aes256GCM = &aead{
+ nK: 256 / 8,
+ nN: 96 / 8,
+ new: newAESGCM,
+ id: 0x0002,
+}
+
+var chacha20poly1305AEAD = &aead{
+ nK: chacha20poly1305.KeySize,
+ nN: chacha20poly1305.NonceSize,
+ new: chacha20poly1305.New,
+ id: 0x0003,
+}
+
+func newAESGCM(key []byte) (cipher.AEAD, error) {
+ b, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ return cipher.NewGCM(b)
+}
+
+func (a *aead) ID() uint16 {
+ return a.id
+}
+
+func (a *aead) aead(key []byte) (cipher.AEAD, error) {
+ if len(key) != a.nK {
+ return nil, errors.New("invalid key size")
+ }
+ return a.new(key)
+}
+
+func (a *aead) keySize() int {
+ return a.nK
+}
+
+func (a *aead) nonceSize() int {
+ return a.nN
+}
+
+type exportOnlyAEAD struct{}
+
+func (exportOnlyAEAD) ID() uint16 {
+ return 0xFFFF
+}
+
+func (exportOnlyAEAD) aead(key []byte) (cipher.AEAD, error) {
+ return nil, nil
+}
+
+func (exportOnlyAEAD) keySize() int {
+ return 0
+}
+
+func (exportOnlyAEAD) nonceSize() int {
+ return 0
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// Package hpke implements Hybrid Public Key Encryption (HPKE) as defined in
+// [RFC 9180].
+//
+// [RFC 9180]: https://www.rfc-editor.org/rfc/rfc9180.html
package hpke
import (
- "crypto"
- "crypto/aes"
"crypto/cipher"
- "crypto/ecdh"
- "crypto/hkdf"
- "crypto/rand"
+ "encoding/binary"
"errors"
- "internal/byteorder"
- "math/bits"
-
- "golang.org/x/crypto/chacha20poly1305"
)
-// testingOnlyGenerateKey is only used during testing, to provide
-// a fixed test key to use when checking the RFC 9180 vectors.
-var testingOnlyGenerateKey func() (*ecdh.PrivateKey, error)
-
-type hkdfKDF struct {
- hash crypto.Hash
-}
-
-func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputKey []byte) ([]byte, error) {
- labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey))
- labeledIKM = append(labeledIKM, []byte("HPKE-v1")...)
- labeledIKM = append(labeledIKM, sid...)
- labeledIKM = append(labeledIKM, label...)
- labeledIKM = append(labeledIKM, inputKey...)
- return hkdf.Extract(kdf.hash.New, labeledIKM, salt)
-}
-
-func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) ([]byte, error) {
- labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info))
- labeledInfo = byteorder.BEAppendUint16(labeledInfo, length)
- labeledInfo = append(labeledInfo, []byte("HPKE-v1")...)
- labeledInfo = append(labeledInfo, suiteID...)
- labeledInfo = append(labeledInfo, label...)
- labeledInfo = append(labeledInfo, info...)
- return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length))
-}
-
-// dhKEM implements the KEM specified in RFC 9180, Section 4.1.
-type dhKEM struct {
- dh ecdh.Curve
- kdf hkdfKDF
-
- suiteID []byte
- nSecret uint16
-}
-
-type KemID uint16
-
-const DHKEM_X25519_HKDF_SHA256 = 0x0020
-
-var SupportedKEMs = map[uint16]struct {
- curve ecdh.Curve
- hash crypto.Hash
- nSecret uint16
-}{
- // RFC 9180 Section 7.1
- DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32},
-}
-
-func newDHKem(kemID uint16) (*dhKEM, error) {
- suite, ok := SupportedKEMs[kemID]
- if !ok {
- return nil, errors.New("unsupported suite ID")
- }
- return &dhKEM{
- dh: suite.curve,
- kdf: hkdfKDF{suite.hash},
- suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID),
- nSecret: suite.nSecret,
- }, nil
-}
-
-func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) ([]byte, error) {
- eaePRK, err := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey)
- if err != nil {
- return nil, err
- }
- return dh.kdf.LabeledExpand(dh.suiteID[:], eaePRK, "shared_secret", kemContext, dh.nSecret)
-}
-
-func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encapPub []byte, err error) {
- var privEph *ecdh.PrivateKey
- if testingOnlyGenerateKey != nil {
- privEph, err = testingOnlyGenerateKey()
- } else {
- privEph, err = dh.dh.GenerateKey(rand.Reader)
- }
- if err != nil {
- return nil, nil, err
- }
- dhVal, err := privEph.ECDH(pubRecipient)
- if err != nil {
- return nil, nil, err
- }
- encPubEph := privEph.PublicKey().Bytes()
-
- encPubRecip := pubRecipient.Bytes()
- kemContext := append(encPubEph, encPubRecip...)
- sharedSecret, err = dh.ExtractAndExpand(dhVal, kemContext)
- if err != nil {
- return nil, nil, err
- }
- return sharedSecret, encPubEph, nil
-}
-
-func (dh *dhKEM) Decap(encPubEph []byte, secRecipient *ecdh.PrivateKey) ([]byte, error) {
- pubEph, err := dh.dh.NewPublicKey(encPubEph)
- if err != nil {
- return nil, err
- }
- dhVal, err := secRecipient.ECDH(pubEph)
- if err != nil {
- return nil, err
- }
- kemContext := append(encPubEph, secRecipient.PublicKey().Bytes()...)
- return dh.ExtractAndExpand(dhVal, kemContext)
-}
-
type context struct {
- aead cipher.AEAD
-
- sharedSecret []byte
-
suiteID []byte
- key []byte
- baseNonce []byte
- exporterSecret []byte
+ export func(string, uint16) ([]byte, error)
- seqNum uint128
+ aead cipher.AEAD
+ baseNonce []byte
+ // seqNum starts at zero and is incremented for each Seal/Open call.
+ // 64 bits are enough not to overflow for 500 years at 1ns per operation.
+ seqNum uint64
}
+// Sender is a sending HPKE context. It is instantiated with a specific KEM
+// encapsulation key (i.e. the public key), and it is stateful, incrementing the
+// nonce counter for each [Sender.Seal] call.
type Sender struct {
*context
}
+// Recipient is a receiving HPKE context. It is instantiated with a specific KEM
+// decapsulation key (i.e. the secret key), and it is stateful, incrementing the
+// nonce counter for each successful [Recipient.Open] call.
type Recipient struct {
*context
}
-var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
- block, err := aes.NewCipher(key)
- if err != nil {
- return nil, err
- }
- return cipher.NewGCM(block)
-}
-
-type AEADID uint16
-
-const (
- AEAD_AES_128_GCM = 0x0001
- AEAD_AES_256_GCM = 0x0002
- AEAD_ChaCha20Poly1305 = 0x0003
-)
-
-var SupportedAEADs = map[uint16]struct {
- keySize int
- nonceSize int
- aead func([]byte) (cipher.AEAD, error)
-}{
- // RFC 9180, Section 7.3
- AEAD_AES_128_GCM: {keySize: 16, nonceSize: 12, aead: aesGCMNew},
- AEAD_AES_256_GCM: {keySize: 32, nonceSize: 12, aead: aesGCMNew},
- AEAD_ChaCha20Poly1305: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New},
-}
-
-type KDFID uint16
+func newContext(sharedSecret []byte, kemID uint16, kdf KDF, aead AEAD, info []byte) (*context, error) {
+ sid := suiteID(kemID, kdf.ID(), aead.ID())
-const KDF_HKDF_SHA256 = 0x0001
-
-var SupportedKDFs = map[uint16]func() *hkdfKDF{
- // RFC 9180, Section 7.2
- KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} },
-}
-
-func newContext(sharedSecret []byte, kemID, kdfID, aeadID uint16, info []byte) (*context, error) {
- sid := suiteID(kemID, kdfID, aeadID)
-
- kdfInit, ok := SupportedKDFs[kdfID]
- if !ok {
- return nil, errors.New("unsupported KDF id")
- }
- kdf := kdfInit()
-
- aeadInfo, ok := SupportedAEADs[aeadID]
- if !ok {
- return nil, errors.New("unsupported AEAD id")
- }
-
- pskIDHash, err := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil)
+ pskIDHash, err := kdf.labeledExtract(sid, nil, "psk_id_hash", nil)
if err != nil {
return nil, err
}
- infoHash, err := kdf.LabeledExtract(sid, nil, "info_hash", info)
+ infoHash, err := kdf.labeledExtract(sid, nil, "info_hash", info)
if err != nil {
return nil, err
}
ksContext := append([]byte{0}, pskIDHash...)
ksContext = append(ksContext, infoHash...)
- secret, err := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
+ secret, err := kdf.labeledExtract(sid, sharedSecret, "secret", nil)
if err != nil {
return nil, err
}
- key, err := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
+ key, err := kdf.labeledExpand(sid, secret, "key", ksContext, uint16(aead.keySize()))
if err != nil {
return nil, err
}
- baseNonce, err := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */)
+ a, err := aead.aead(key)
if err != nil {
return nil, err
}
- exporterSecret, err := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/)
+ baseNonce, err := kdf.labeledExpand(sid, secret, "base_nonce", ksContext, uint16(aead.nonceSize()))
if err != nil {
return nil, err
}
-
- aead, err := aeadInfo.aead(key)
+ expSecret, err := kdf.labeledExpand(sid, secret, "exp", ksContext, uint16(len(secret)))
if err != nil {
return nil, err
}
+ export := func(exporterContext string, length uint16) ([]byte, error) {
+ return kdf.labeledExpand(sid, expSecret, "sec", []byte(exporterContext), length)
+ }
return &context{
- aead: aead,
- sharedSecret: sharedSecret,
- suiteID: sid,
- key: key,
- baseNonce: baseNonce,
- exporterSecret: exporterSecret,
+ aead: a,
+ suiteID: sid,
+ export: export,
+ baseNonce: baseNonce,
}, nil
}
-func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) {
- kem, err := newDHKem(kemID)
+// NewSender returns a sending HPKE context for the provided KEM encapsulation
+// key (i.e. the public key), and using the ciphersuite defined by the
+// combination of KEM, KDF, and AEAD.
+//
+// The info parameter is additional public information that must match between
+// sender and recipient.
+//
+// The returned enc ciphertext can be used to instantiate a matching receiving
+// HPKE context with the corresponding KEM decapsulation key.
+func NewSender(kem KEMSender, kdf KDF, aead AEAD, info []byte) (enc []byte, s *Sender, err error) {
+ sharedSecret, encapsulatedKey, err := kem.encap()
if err != nil {
return nil, nil, err
}
- sharedSecret, encapsulatedKey, err := kem.Encap(pub)
+ context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
if err != nil {
return nil, nil, err
}
-
- context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
- if err != nil {
- return nil, nil, err
- }
-
return encapsulatedKey, &Sender{context}, nil
}
-func SetupRecipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Recipient, error) {
- kem, err := newDHKem(kemID)
+// NewRecipient returns a receiving HPKE context for the provided KEM
+// decapsulation key (i.e. the secret key), and using the ciphersuite defined by
+// the combination of KEM, KDF, and AEAD.
+//
+// The enc parameter must have been produced by a matching sending HPKE context
+// with the corresponding KEM encapsulation key. The info parameter is
+// additional public information that must match between sender and recipient.
+func NewRecipient(enc []byte, kem KEMRecipient, kdf KDF, aead AEAD, info []byte) (*Recipient, error) {
+ sharedSecret, err := kem.decap(enc)
if err != nil {
return nil, err
}
- sharedSecret, err := kem.Decap(encPubEph, priv)
+ context, err := newContext(sharedSecret, kem.ID(), kdf, aead, info)
if err != nil {
return nil, err
}
-
- context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
- if err != nil {
- return nil, err
- }
-
return &Recipient{context}, nil
}
-func (ctx *context) nextNonce() []byte {
- nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():]
- for i := range ctx.baseNonce {
- nonce[i] ^= ctx.baseNonce[i]
+// Seal encrypts the provided plaintext, optionally binding to the additional
+// public data aad.
+//
+// Seal uses incrementing counters for each call, and Open on the receiving side
+// must be called in the same order as Seal.
+func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
+ if s.aead == nil {
+ return nil, errors.New("export-only instantiation")
}
- return nonce
+ ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
+ s.seqNum++
+ return ciphertext, nil
}
-func (ctx *context) incrementNonce() {
- // Message limit is, according to the RFC, 2^95+1, which
- // is somewhat confusing, but we do as we're told.
- if ctx.seqNum.bitLen() >= (ctx.aead.NonceSize()*8)-1 {
- panic("message limit reached")
+// Seal instantiates a single-use HPKE sending HPKE context like [NewSender],
+// and then encrypts the provided plaintext like [Sender.Seal] (with no aad).
+func Seal(kem KEMSender, kdf KDF, aead AEAD, info, plaintext []byte) (enc, ct []byte, err error) {
+ enc, s, err := NewSender(kem, kdf, aead, info)
+ if err != nil {
+ return nil, nil, err
}
- ctx.seqNum = ctx.seqNum.addOne()
+ ct, err = s.Seal(nil, plaintext)
+ if err != nil {
+ return nil, nil, err
+ }
+ return enc, ct, err
}
-func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
- ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
- s.incrementNonce()
- return ciphertext, nil
+// Export produces a secret value derived from the shared key between sender and
+// recipient. length must be at most 65,535.
+func (s *Sender) Export(exporterContext string, length int) ([]byte, error) {
+ if length < 0 || length > 0xFFFF {
+ return nil, errors.New("invalid length")
+ }
+ return s.export(exporterContext, uint16(length))
}
+// Open decrypts the provided ciphertext, optionally binding to the additional
+// public data aad, or returns an error if decryption fails.
+//
+// Open uses incrementing counters for each successful call, and must be called
+// in the same order as Seal on the sending side.
func (r *Recipient) Open(aad, ciphertext []byte) ([]byte, error) {
+ if r.aead == nil {
+ return nil, errors.New("export-only instantiation")
+ }
plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad)
if err != nil {
return nil, err
}
- r.incrementNonce()
+ r.seqNum++
return plaintext, nil
}
-func suiteID(kemID, kdfID, aeadID uint16) []byte {
- suiteID := make([]byte, 0, 4+2+2+2)
- suiteID = append(suiteID, []byte("HPKE")...)
- suiteID = byteorder.BEAppendUint16(suiteID, kemID)
- suiteID = byteorder.BEAppendUint16(suiteID, kdfID)
- suiteID = byteorder.BEAppendUint16(suiteID, aeadID)
- return suiteID
-}
-
-func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) {
- kemInfo, ok := SupportedKEMs[kemID]
- if !ok {
- return nil, errors.New("unsupported KEM id")
+// Open instantiates a single-use HPKE receiving HPKE context like [NewRecipient],
+// and then decrypts the provided ciphertext like [Recipient.Open] (with no aad).
+func Open(enc []byte, kem KEMRecipient, kdf KDF, aead AEAD, info, ciphertext []byte) ([]byte, error) {
+ r, err := NewRecipient(enc, kem, kdf, aead, info)
+ if err != nil {
+ return nil, err
}
- return kemInfo.curve.NewPublicKey(bytes)
+ return r.Open(nil, ciphertext)
}
-func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) {
- kemInfo, ok := SupportedKEMs[kemID]
- if !ok {
- return nil, errors.New("unsupported KEM id")
+// Export produces a secret value derived from the shared key between sender and
+// recipient. length must be at most 65,535.
+func (r *Recipient) Export(exporterContext string, length int) ([]byte, error) {
+ if length < 0 || length > 0xFFFF {
+ return nil, errors.New("invalid length")
}
- return kemInfo.curve.NewPrivateKey(bytes)
-}
-
-type uint128 struct {
- hi, lo uint64
+ return r.export(exporterContext, uint16(length))
}
-func (u uint128) addOne() uint128 {
- lo, carry := bits.Add64(u.lo, 1, 0)
- return uint128{u.hi + carry, lo}
-}
-
-func (u uint128) bitLen() int {
- return bits.Len64(u.hi) + bits.Len64(u.lo)
+func (ctx *context) nextNonce() []byte {
+ nonce := make([]byte, ctx.aead.NonceSize())
+ binary.BigEndian.PutUint64(nonce[len(nonce)-8:], ctx.seqNum)
+ for i := range ctx.baseNonce {
+ nonce[i] ^= ctx.baseNonce[i]
+ }
+ return nonce
}
-func (u uint128) bytes() []byte {
- b := make([]byte, 16)
- byteorder.BEPutUint64(b[0:], u.hi)
- byteorder.BEPutUint64(b[8:], u.lo)
- return b
+func suiteID(kemID, kdfID, aeadID uint16) []byte {
+ suiteID := make([]byte, 0, 4+2+2+2)
+ suiteID = append(suiteID, []byte("HPKE")...)
+ suiteID = binary.BigEndian.AppendUint16(suiteID, kemID)
+ suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID)
+ suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID)
+ return suiteID
}
import (
"bytes"
+ "crypto/ecdh"
+ "crypto/sha3"
"encoding/hex"
"encoding/json"
+ "fmt"
+ "io"
"os"
- "strconv"
- "strings"
"testing"
-
- "crypto/ecdh"
- _ "crypto/sha256"
- _ "crypto/sha512"
)
func mustDecodeHex(t *testing.T, in string) []byte {
return b
}
-func parseVectorSetup(vector string) map[string]string {
- vals := map[string]string{}
- for _, l := range strings.Split(vector, "\n") {
- fields := strings.Split(l, ": ")
- vals[fields[0]] = fields[1]
- }
- return vals
-}
-
-func parseVectorEncryptions(vector string) []map[string]string {
- vals := []map[string]string{}
- for _, section := range strings.Split(vector, "\n\n") {
- e := map[string]string{}
- for _, l := range strings.Split(section, "\n") {
- fields := strings.Split(l, ": ")
- e[fields[0]] = fields[1]
- }
- vals = append(vals, e)
- }
- return vals
-}
-
-func TestRFC9180Vectors(t *testing.T) {
- vectorsJSON, err := os.ReadFile("testdata/rfc9180-vectors.json")
+func TestVectors(t *testing.T) {
+ vectorsJSON, err := os.ReadFile("testdata/rfc9180.json")
if err != nil {
t.Fatal(err)
}
-
var vectors []struct {
- Name string
- Setup string
- Encryptions string
+ Mode uint16 `json:"mode"`
+ KEM uint16 `json:"kem_id"`
+ KDF uint16 `json:"kdf_id"`
+ AEAD uint16 `json:"aead_id"`
+ Info string `json:"info"`
+ IkmE string `json:"ikmE"`
+ IkmR string `json:"ikmR"`
+ SkRm string `json:"skRm"`
+ PkRm string `json:"pkRm"`
+ Enc string `json:"enc"`
+ Encryptions string `json:"encryptions"`
+ Exports string `json:"exports"`
}
if err := json.Unmarshal(vectorsJSON, &vectors); err != nil {
t.Fatal(err)
}
for _, vector := range vectors {
- t.Run(vector.Name, func(t *testing.T) {
- setup := parseVectorSetup(vector.Setup)
-
- kemID, err := strconv.Atoi(setup["kem_id"])
- if err != nil {
- t.Fatal(err)
+ name := fmt.Sprintf("mode %04x kem %04x kdf %04x aead %04x",
+ vector.Mode, vector.KEM, vector.KDF, vector.AEAD)
+ t.Run(name, func(t *testing.T) {
+ if vector.Mode != 0 {
+ t.Skip("only mode 0 (base) is supported")
}
- if _, ok := SupportedKEMs[uint16(kemID)]; !ok {
- t.Skip("unsupported KEM")
+ if vector.KEM == 0x0021 {
+ t.Skip("KEM 0x0021 (DHKEM(X448)) not supported")
}
- kdfID, err := strconv.Atoi(setup["kdf_id"])
+
+ kdf, err := NewKDF(vector.KDF)
if err != nil {
t.Fatal(err)
}
- if _, ok := SupportedKDFs[uint16(kdfID)]; !ok {
- t.Skip("unsupported KDF")
+ if kdf.ID() != vector.KDF {
+ t.Errorf("unexpected KDF ID: got %04x, want %04x", kdf.ID(), vector.KDF)
}
- aeadID, err := strconv.Atoi(setup["aead_id"])
+
+ aead, err := NewAEAD(vector.AEAD)
if err != nil {
t.Fatal(err)
}
- if _, ok := SupportedAEADs[uint16(aeadID)]; !ok {
- t.Skip("unsupported AEAD")
+ if aead.ID() != vector.AEAD {
+ t.Errorf("unexpected AEAD ID: got %04x, want %04x", aead.ID(), vector.AEAD)
}
- info := mustDecodeHex(t, setup["info"])
- pubKeyBytes := mustDecodeHex(t, setup["pkRm"])
- pub, err := ParseHPKEPublicKey(uint16(kemID), pubKeyBytes)
+ pubKeyBytes := mustDecodeHex(t, vector.PkRm)
+ kemSender, err := NewKEMSender(vector.KEM, pubKeyBytes)
if err != nil {
t.Fatal(err)
}
-
- ephemeralPrivKey := mustDecodeHex(t, setup["skEm"])
-
- testingOnlyGenerateKey = func() (*ecdh.PrivateKey, error) {
- return SupportedKEMs[uint16(kemID)].curve.NewPrivateKey(ephemeralPrivKey)
+ if kemSender.ID() != vector.KEM {
+ t.Errorf("unexpected KEM ID: got %04x, want %04x", kemSender.ID(), vector.KEM)
+ }
+ if !bytes.Equal(kemSender.Bytes(), pubKeyBytes) {
+ t.Errorf("unexpected KEM bytes: got %x, want %x", kemSender.Bytes(), pubKeyBytes)
}
- t.Cleanup(func() { testingOnlyGenerateKey = nil })
- encap, sender, err := SetupSender(
- uint16(kemID),
- uint16(kdfID),
- uint16(aeadID),
- pub,
- info,
- )
+ ikmE := mustDecodeHex(t, vector.IkmE)
+ setupDerandomizedEncap(t, vector.KEM, ikmE)
+
+ info := mustDecodeHex(t, vector.Info)
+ encap, sender, err := NewSender(kemSender, kdf, aead, info)
if err != nil {
t.Fatal(err)
}
- expectedEncap := mustDecodeHex(t, setup["enc"])
+ expectedEncap := mustDecodeHex(t, vector.Enc)
if !bytes.Equal(encap, expectedEncap) {
t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap)
}
- privKeyBytes := mustDecodeHex(t, setup["skRm"])
- priv, err := ParseHPKEPrivateKey(uint16(kemID), privKeyBytes)
+ privKeyBytes := mustDecodeHex(t, vector.SkRm)
+ kemRecipient, err := NewKEMRecipient(vector.KEM, privKeyBytes)
if err != nil {
t.Fatal(err)
}
-
- recipient, err := SetupRecipient(
- uint16(kemID),
- uint16(kdfID),
- uint16(aeadID),
- priv,
- info,
- encap,
- )
+ if kemRecipient.ID() != vector.KEM {
+ t.Errorf("unexpected KEM ID: got %04x, want %04x", kemRecipient.ID(), vector.KEM)
+ }
+ kemRecipientBytes, err := kemRecipient.Bytes()
if err != nil {
t.Fatal(err)
}
-
- for _, ctx := range []*context{sender.context, recipient.context} {
- expectedSharedSecret := mustDecodeHex(t, setup["shared_secret"])
- if !bytes.Equal(ctx.sharedSecret, expectedSharedSecret) {
- t.Errorf("unexpected shared secret, got: %x, want %x", ctx.sharedSecret, expectedSharedSecret)
+ // X25519 serialized keys must be clamped, so the bytes might not match.
+ if !bytes.Equal(kemRecipientBytes, privKeyBytes) && vector.KEM != dhkemX25519 {
+ t.Errorf("unexpected KEM bytes: got %x, want %x", kemRecipientBytes, privKeyBytes)
+ }
+ if vector.KEM == dhkemX25519 {
+ kem2, err := NewKEMRecipient(vector.KEM, kemRecipientBytes)
+ if err != nil {
+ t.Fatal(err)
}
- expectedKey := mustDecodeHex(t, setup["key"])
- if !bytes.Equal(ctx.key, expectedKey) {
- t.Errorf("unexpected key, got: %x, want %x", ctx.key, expectedKey)
+ kemRecipientBytes2, err := kem2.Bytes()
+ if err != nil {
+ t.Fatal(err)
}
- expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"])
- if !bytes.Equal(ctx.baseNonce, expectedBaseNonce) {
- t.Errorf("unexpected base nonce, got: %x, want %x", ctx.baseNonce, expectedBaseNonce)
+ if !bytes.Equal(kemRecipientBytes2, kemRecipientBytes) {
+ t.Errorf("X25519 re-serialized key differs: got %x, want %x", kemRecipientBytes2, kemRecipientBytes)
}
- expectedExporterSecret := mustDecodeHex(t, setup["exporter_secret"])
- if !bytes.Equal(ctx.exporterSecret, expectedExporterSecret) {
- t.Errorf("unexpected exporter secret, got: %x, want %x", ctx.exporterSecret, expectedExporterSecret)
+ if !bytes.Equal(kem2.KEMSender().Bytes(), pubKeyBytes) {
+ t.Errorf("X25519 re-derived public key differs: got %x, want %x", kem2.KEMSender().Bytes(), pubKeyBytes)
}
}
+ if !bytes.Equal(kemRecipient.KEMSender().Bytes(), pubKeyBytes) {
+ t.Errorf("unexpected KEM sender bytes: got %x, want %x", kemRecipient.KEMSender().Bytes(), pubKeyBytes)
+ }
- for _, enc := range parseVectorEncryptions(vector.Encryptions) {
- t.Run("seq num "+enc["sequence number"], func(t *testing.T) {
- seqNum, err := strconv.Atoi(enc["sequence number"])
- if err != nil {
- t.Fatal(err)
- }
- sender.seqNum = uint128{lo: uint64(seqNum)}
- recipient.seqNum = uint128{lo: uint64(seqNum)}
- expectedNonce := mustDecodeHex(t, enc["nonce"])
- computedNonce := sender.nextNonce()
- if !bytes.Equal(computedNonce, expectedNonce) {
- t.Errorf("unexpected nonce: got %x, want %x", computedNonce, expectedNonce)
- }
+ seed := mustDecodeHex(t, vector.IkmR)
+ seedRecipient, err := NewKEMRecipientFromSeed(vector.KEM, seed)
+ if err != nil {
+ t.Fatal(err)
+ }
+ seedRecipientBytes, err := seedRecipient.Bytes()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(seedRecipientBytes, privKeyBytes) && vector.KEM != 0x0020 {
+ t.Errorf("unexpected KEM bytes from seed: got %x, want %x", seedRecipientBytes, privKeyBytes)
+ }
+ if !bytes.Equal(seedRecipient.KEMSender().Bytes(), pubKeyBytes) {
+ t.Errorf("unexpected KEM sender bytes from seed: got %x, want %x", seedRecipient.KEMSender().Bytes(), pubKeyBytes)
+ }
+
+ recipient, err := NewRecipient(encap, kemRecipient, kdf, aead, info)
+ if err != nil {
+ t.Fatal(err)
+ }
- expectedCiphertext := mustDecodeHex(t, enc["ct"])
- ciphertext, err := sender.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"]))
+ if aead != ExportOnly() {
+ source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
+ for range 1000 {
+ aad, plaintext := drawRandomInput(t, source), drawRandomInput(t, source)
+ ciphertext, err := sender.Seal(aad, plaintext)
if err != nil {
t.Fatal(err)
}
- if !bytes.Equal(ciphertext, expectedCiphertext) {
- t.Errorf("unexpected ciphertext: got %x want %x", ciphertext, expectedCiphertext)
- }
-
- expectedPlaintext := mustDecodeHex(t, enc["pt"])
- plaintext, err := recipient.Open(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["ct"]))
+ sink.Write(ciphertext)
+ got, err := recipient.Open(aad, ciphertext)
if err != nil {
t.Fatal(err)
}
- if !bytes.Equal(plaintext, expectedPlaintext) {
- t.Errorf("unexpected plaintext: got %x want %x", plaintext, expectedPlaintext)
+ if !bytes.Equal(got, plaintext) {
+ t.Errorf("unexpected plaintext: got %x want %x", got, plaintext)
}
- })
+ }
+ encryptions := make([]byte, 16)
+ sink.Read(encryptions)
+ expectedEncryptions := mustDecodeHex(t, vector.Encryptions)
+ if !bytes.Equal(encryptions, expectedEncryptions) {
+ t.Errorf("unexpected accumulated encryptions, got: %x, want %x", encryptions, expectedEncryptions)
+ }
+ } else {
+ if _, err := sender.Seal(nil, nil); err == nil {
+ t.Error("expected error from Seal with export-only AEAD")
+ }
+ if _, err := recipient.Open(nil, nil); err == nil {
+ t.Error("expected error from Open with export-only AEAD")
+ }
+ }
+
+ source, sink := sha3.NewSHAKE128(), sha3.NewSHAKE128()
+ for l := range 1000 {
+ context := string(drawRandomInput(t, source))
+ value, err := sender.Export(context, l)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sink.Write(value)
+ got, err := recipient.Export(context, l)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(got, value) {
+ t.Errorf("recipient: unexpected exported secret: got %x want %x", got, value)
+ }
+ }
+ exports := make([]byte, 16)
+ sink.Read(exports)
+ expectedExports := mustDecodeHex(t, vector.Exports)
+ if !bytes.Equal(exports, expectedExports) {
+ t.Errorf("unexpected accumulated exports, got: %x, want %x", exports, expectedExports)
}
})
}
}
+
+func drawRandomInput(t *testing.T, r io.Reader) []byte {
+ t.Helper()
+ l := make([]byte, 1)
+ if _, err := r.Read(l); err != nil {
+ t.Fatal(err)
+ }
+ n := int(l[0])
+ b := make([]byte, n)
+ if _, err := r.Read(b); err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
+
+func setupDerandomizedEncap(t *testing.T, kemID uint16, randBytes []byte) {
+ r, err := NewKEMRecipientFromSeed(kemID, randBytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+ testingOnlyGenerateKey = func() *ecdh.PrivateKey {
+ return r.(*dhKEMRecipient).priv.(*ecdh.PrivateKey)
+ }
+ t.Cleanup(func() {
+ testingOnlyGenerateKey = nil
+ })
+}
+
+func TestSingletons(t *testing.T) {
+ if HKDFSHA256() != HKDFSHA256() {
+ t.Error("HKDFSHA256() != HKDFSHA256()")
+ }
+ if HKDFSHA384() != HKDFSHA384() {
+ t.Error("HKDFSHA384() != HKDFSHA384()")
+ }
+ if HKDFSHA512() != HKDFSHA512() {
+ t.Error("HKDFSHA512() != HKDFSHA512()")
+ }
+ if AES128GCM() != AES128GCM() {
+ t.Error("AES128GCM() != AES128GCM()")
+ }
+ if AES256GCM() != AES256GCM() {
+ t.Error("AES256GCM() != AES256GCM()")
+ }
+ if ChaCha20Poly1305() != ChaCha20Poly1305() {
+ t.Error("ChaCha20Poly1305() != ChaCha20Poly1305()")
+ }
+ if ExportOnly() != ExportOnly() {
+ t.Error("ExportOnly() != ExportOnly()")
+ }
+}
--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package hpke
+
+import (
+ "crypto/hkdf"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/binary"
+ "fmt"
+ "hash"
+)
+
+// The KDF is one of the three components of an HPKE ciphersuite, implementing
+// key derivation.
+type KDF interface {
+ ID() uint16
+ labeledExtract(sid, salt []byte, label string, inputKey []byte) ([]byte, error)
+ labeledExpand(suiteID, randomKey []byte, label string, info []byte, length uint16) ([]byte, error)
+}
+
+// NewKDF returns the KDF implementation for the given KDF ID.
+//
+// Applications are encouraged to use specific implementations like [HKDFSHA256]
+// instead, unless runtime agility is required.
+func NewKDF(id uint16) (KDF, error) {
+ switch id {
+ case 0x0001: // HKDF-SHA256
+ return HKDFSHA256(), nil
+ case 0x0002: // HKDF-SHA384
+ return HKDFSHA384(), nil
+ case 0x0003: // HKDF-SHA512
+ return HKDFSHA512(), nil
+ default:
+ return nil, fmt.Errorf("unsupported KDF %04x", id)
+ }
+}
+
+// HKDFSHA256 returns an HKDF-SHA256 KDF implementation.
+func HKDFSHA256() KDF { return hkdfSHA256 }
+
+// HKDFSHA384 returns an HKDF-SHA384 KDF implementation.
+func HKDFSHA384() KDF { return hkdfSHA384 }
+
+// HKDFSHA512 returns an HKDF-SHA512 KDF implementation.
+func HKDFSHA512() KDF { return hkdfSHA512 }
+
+type hkdfKDF struct {
+ hash func() hash.Hash
+ id uint16
+}
+
+var hkdfSHA256 = &hkdfKDF{hash: sha256.New, id: 0x0001}
+var hkdfSHA384 = &hkdfKDF{hash: sha512.New384, id: 0x0002}
+var hkdfSHA512 = &hkdfKDF{hash: sha512.New, id: 0x0003}
+
+func (kdf *hkdfKDF) ID() uint16 {
+ return kdf.id
+}
+
+func (kdf *hkdfKDF) labeledExtract(sid []byte, salt []byte, label string, inputKey []byte) ([]byte, error) {
+ labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey))
+ labeledIKM = append(labeledIKM, []byte("HPKE-v1")...)
+ labeledIKM = append(labeledIKM, sid...)
+ labeledIKM = append(labeledIKM, label...)
+ labeledIKM = append(labeledIKM, inputKey...)
+ return hkdf.Extract(kdf.hash, labeledIKM, salt)
+}
+
+func (kdf *hkdfKDF) labeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) ([]byte, error) {
+ labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info))
+ labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length)
+ labeledInfo = append(labeledInfo, []byte("HPKE-v1")...)
+ labeledInfo = append(labeledInfo, suiteID...)
+ labeledInfo = append(labeledInfo, label...)
+ labeledInfo = append(labeledInfo, info...)
+ return hkdf.Expand(kdf.hash, randomKey, string(labeledInfo), int(length))
+}
--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package hpke
+
+import (
+ "crypto/ecdh"
+ "crypto/rand"
+ "encoding/binary"
+ "errors"
+)
+
+const (
+ dhkemP256 = 0x0010 // DHKEM(P-256, HKDF-SHA256)
+ dhkemP384 = 0x0011 // DHKEM(P-384, HKDF-SHA384)
+ dhkemP521 = 0x0012 // DHKEM(P-521, HKDF-SHA512)
+ dhkemX25519 = 0x0020 // DHKEM(X25519, HKDF-SHA256)
+)
+
+// A KEMSender is an instantiation of a KEM (one of the three components of an
+// HPKE ciphersuite) with an encapsulation key (i.e. the public key).
+type KEMSender interface {
+ // ID returns the HPKE KEM identifier.
+ ID() uint16
+
+ // Bytes returns the public key as the output of SerializePublicKey.
+ Bytes() []byte
+
+ encap() (sharedSecret, enc []byte, err error)
+}
+
+// NewKEMSender implements DeserializePublicKey and returns a KEMSender
+// for the given KEM ID and public key bytes.
+//
+// Applications are encouraged to use [ecdh.Curve.NewPublicKey] with
+// [NewECDHSender] instead, unless runtime agility is required.
+func NewKEMSender(id uint16, pub []byte) (KEMSender, error) {
+ switch id {
+ case dhkemP256:
+ k, err := ecdh.P256().NewPublicKey(pub)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHSender(k)
+ case dhkemP384:
+ k, err := ecdh.P384().NewPublicKey(pub)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHSender(k)
+ case dhkemP521:
+ k, err := ecdh.P521().NewPublicKey(pub)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHSender(k)
+ case dhkemX25519:
+ k, err := ecdh.X25519().NewPublicKey(pub)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHSender(k)
+ default:
+ return nil, errors.New("unsupported KEM")
+ }
+}
+
+// A KEMRecipient is an instantiation of a KEM (one of the three components of
+// an HPKE ciphersuite) with a decapsulation key (i.e. the secret key).
+type KEMRecipient interface {
+ // ID returns the HPKE KEM identifier.
+ ID() uint16
+
+ // Bytes returns the private key as the output of SerializePrivateKey, as
+ // defined in RFC 9180.
+ //
+ // Note that for X25519 this might not match the input to NewPrivateKey.
+ // This is a requirement of RFC 9180, Section 7.1.2.
+ Bytes() ([]byte, error)
+
+ // KEMSender returns the corresponding KEMSender for this recipient.
+ KEMSender() KEMSender
+
+ decap(enc []byte) (sharedSecret []byte, err error)
+}
+
+// NewKEMRecipient implements DeserializePrivateKey, as defined in RFC 9180, and
+// returns a KEMRecipient for the given KEM ID and private key bytes.
+//
+// Applications are encouraged to use [ecdh.Curve.NewPrivateKey] with
+// [NewECDHRecipient] instead, unless runtime agility is required.
+func NewKEMRecipient(id uint16, priv []byte) (KEMRecipient, error) {
+ switch id {
+ case dhkemP256:
+ k, err := ecdh.P256().NewPrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHRecipient(k)
+ case dhkemP384:
+ k, err := ecdh.P384().NewPrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHRecipient(k)
+ case dhkemP521:
+ k, err := ecdh.P521().NewPrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHRecipient(k)
+ case dhkemX25519:
+ k, err := ecdh.X25519().NewPrivateKey(priv)
+ if err != nil {
+ return nil, err
+ }
+ return NewECDHRecipient(k)
+ default:
+ return nil, errors.New("unsupported KEM")
+ }
+}
+
+// NewKEMRecipientFromSeed implements DeriveKeyPair, as defined in RFC 9180, and
+// returns a KEMRecipient for the given KEM ID and private key seed.
+//
+// Currently, it only supports the KEMs based on ECDH (DHKEM).
+func NewKEMRecipientFromSeed(id uint16, seed []byte) (KEMRecipient, error) {
+ // DeriveKeyPair from RFC 9180 Section 7.1.3.
+ var curve ecdh.Curve
+ var dh dhKEM
+ var Nsk uint16
+ switch id {
+ case dhkemP256:
+ curve = ecdh.P256()
+ dh, _ = dhKEMForCurve(curve)
+ Nsk = 32
+ case dhkemP384:
+ curve = ecdh.P384()
+ dh, _ = dhKEMForCurve(curve)
+ Nsk = 48
+ case dhkemP521:
+ curve = ecdh.P521()
+ dh, _ = dhKEMForCurve(curve)
+ Nsk = 66
+ case dhkemX25519:
+ curve = ecdh.X25519()
+ dh, _ = dhKEMForCurve(curve)
+ Nsk = 32
+ default:
+ return nil, errors.New("unsupported KEM")
+ }
+ suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), dh.id)
+ prk, err := dh.kdf.labeledExtract(suiteID, nil, "dkp_prk", seed)
+ if err != nil {
+ return nil, err
+ }
+ if id == dhkemX25519 {
+ s, err := dh.kdf.labeledExpand(suiteID, prk, "sk", nil, Nsk)
+ if err != nil {
+ return nil, err
+ }
+ return NewKEMRecipient(id, s)
+ }
+ var counter uint8
+ for counter < 4 {
+ s, err := dh.kdf.labeledExpand(suiteID, prk, "candidate", []byte{counter}, Nsk)
+ if err != nil {
+ return nil, err
+ }
+ if id == dhkemP521 {
+ s[0] &= 0x01
+ }
+ r, err := NewKEMRecipient(id, s)
+ if err != nil {
+ counter++
+ continue
+ }
+ return r, nil
+ }
+ panic("chance of four rejections is < 2^-128")
+}
+
+type dhKEM struct {
+ kdf KDF
+ id uint16
+ nSecret uint16
+}
+
+func (dh *dhKEM) extractAndExpand(dhKey, kemContext []byte) ([]byte, error) {
+ suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), dh.id)
+ eaePRK, err := dh.kdf.labeledExtract(suiteID, nil, "eae_prk", dhKey)
+ if err != nil {
+ return nil, err
+ }
+ return dh.kdf.labeledExpand(suiteID, eaePRK, "shared_secret", kemContext, dh.nSecret)
+}
+
+func (dh *dhKEM) ID() uint16 {
+ return dh.id
+}
+
+type dhKEMSender struct {
+ dhKEM
+ pub *ecdh.PublicKey
+}
+
+// NewECDHSender returns a KEMSender implementing one of
+//
+// - DHKEM(P-256, HKDF-SHA256)
+// - DHKEM(P-384, HKDF-SHA384)
+// - DHKEM(P-521, HKDF-SHA512)
+// - DHKEM(X25519, HKDF-SHA256)
+//
+// depending on the underlying curve of the provided public key.
+func NewECDHSender(pub *ecdh.PublicKey) (KEMSender, error) {
+ dhKEM, err := dhKEMForCurve(pub.Curve())
+ if err != nil {
+ return nil, err
+ }
+ return &dhKEMSender{
+ pub: pub,
+ dhKEM: dhKEM,
+ }, nil
+}
+
+func dhKEMForCurve(curve ecdh.Curve) (dhKEM, error) {
+ switch curve {
+ case ecdh.P256():
+ return dhKEM{
+ kdf: HKDFSHA256(),
+ id: dhkemP256,
+ nSecret: 32,
+ }, nil
+ case ecdh.P384():
+ return dhKEM{
+ kdf: HKDFSHA384(),
+ id: dhkemP384,
+ nSecret: 48,
+ }, nil
+ case ecdh.P521():
+ return dhKEM{
+ kdf: HKDFSHA512(),
+ id: dhkemP521,
+ nSecret: 64,
+ }, nil
+ case ecdh.X25519():
+ return dhKEM{
+ kdf: HKDFSHA256(),
+ id: dhkemX25519,
+ nSecret: 32,
+ }, nil
+ default:
+ return dhKEM{}, errors.New("unsupported curve")
+ }
+}
+
+func (dh *dhKEMSender) Bytes() []byte {
+ return dh.pub.Bytes()
+}
+
+// testingOnlyGenerateKey is only used during testing, to provide
+// a fixed test key to use when checking the RFC 9180 vectors.
+var testingOnlyGenerateKey func() *ecdh.PrivateKey
+
+func (dh *dhKEMSender) encap() (sharedSecret []byte, encapPub []byte, err error) {
+ privEph, err := dh.pub.Curve().GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, nil, err
+ }
+ if testingOnlyGenerateKey != nil {
+ privEph = testingOnlyGenerateKey()
+ }
+ dhVal, err := privEph.ECDH(dh.pub)
+ if err != nil {
+ return nil, nil, err
+ }
+ encPubEph := privEph.PublicKey().Bytes()
+
+ encPubRecip := dh.pub.Bytes()
+ kemContext := append(encPubEph, encPubRecip...)
+ sharedSecret, err = dh.extractAndExpand(dhVal, kemContext)
+ if err != nil {
+ return nil, nil, err
+ }
+ return sharedSecret, encPubEph, nil
+}
+
+type dhKEMRecipient struct {
+ dhKEM
+ priv ecdh.KeyExchanger
+}
+
+// NewECDHRecipient returns a KEMRecipient implementing one of
+//
+// - DHKEM(P-256, HKDF-SHA256)
+// - DHKEM(P-384, HKDF-SHA384)
+// - DHKEM(P-521, HKDF-SHA512)
+// - DHKEM(X25519, HKDF-SHA256)
+//
+// depending on the underlying curve of the provided private key.
+func NewECDHRecipient(priv ecdh.KeyExchanger) (KEMRecipient, error) {
+ dhKEM, err := dhKEMForCurve(priv.Curve())
+ if err != nil {
+ return nil, err
+ }
+ return &dhKEMRecipient{
+ priv: priv,
+ dhKEM: dhKEM,
+ }, nil
+}
+
+func (dh *dhKEMRecipient) Bytes() ([]byte, error) {
+ // Bizarrely, RFC 9180, Section 7.1.2 says SerializePrivateKey MUST clamp
+ // the output, which I thought we all agreed to instead do as part of the DH
+ // function, letting private keys be random bytes.
+ //
+ // At the same time, it says DeserializePrivateKey MUST also clamp, implying
+ // that the input doesn't have to be clamped, so Bytes by spec doesn't
+ // necessarily match the NewPrivateKey input.
+ //
+ // I'm sure this will not lead to any unexpected behavior or interop issue.
+ priv, ok := dh.priv.(*ecdh.PrivateKey)
+ if !ok {
+ return nil, errors.New("ecdh: private key does not support Bytes")
+ }
+ if dh.id == dhkemX25519 {
+ b := priv.Bytes()
+ b[0] &= 248
+ b[31] &= 127
+ b[31] |= 64
+ return b, nil
+ }
+ return priv.Bytes(), nil
+}
+
+func (dh *dhKEMRecipient) KEMSender() KEMSender {
+ return &dhKEMSender{
+ pub: dh.priv.PublicKey(),
+ dhKEM: dh.dhKEM,
+ }
+}
+
+func (dh *dhKEMRecipient) decap(encPubEph []byte) ([]byte, error) {
+ pubEph, err := dh.priv.Curve().NewPublicKey(encPubEph)
+ if err != nil {
+ return nil, err
+ }
+ dhVal, err := dh.priv.ECDH(pubEph)
+ if err != nil {
+ return nil, err
+ }
+ kemContext := append(encPubEph, dh.priv.PublicKey().Bytes()...)
+ return dh.extractAndExpand(dhVal, kemContext)
+}
+++ /dev/null
-[{"Name":"DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM","Setup":"mode: 0\nkem_id: 32\nkdf_id: 1\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234\npkEm: 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431\nskEm: 52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736\nikmR: 6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037\npkRm: 3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d\nskRm: 4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8\nenc: 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431\nshared_secret: fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc\nkey_schedule_context: 00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449\nsecret: 12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397\nkey: 4531685d41d65f03dc48f6b8302c05b0\nbase_nonce: 56d890e5accaaf011cff4b7d\nexporter_secret: 45ff1c2e220db587171952c0592d5f5ebe103f1561a2614e38f2ffd47e99e3f8","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 56d890e5accaaf011cff4b7d\nct: f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 56d890e5accaaf011cff4b7c\nct: af2d7e9ac9ae7e270f46ba1f975be53c09f8d875bdc8535458c2494e8a6eab251c03d0c22a56b8ca42c2063b84\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 56d890e5accaaf011cff4b7f\nct: 498dfcabd92e8acedc281e85af1cb4e3e31c7dc394a1ca20e173cb72516491588d96a19ad4a683518973dcc180\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 56d890e5accaaf011cff4b79\nct: 583bd32bc67a5994bb8ceaca813d369bca7b2a42408cddef5e22f880b631215a09fc0012bc69fccaa251c0246d\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 56d890e5accaaf011cff4b82\nct: 7175db9717964058640a3a11fb9007941a5d1757fda1a6935c805c21af32505bf106deefec4a49ac38d71c9e0a\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 56d890e5accaaf011cff4a7d\nct: 957f9800542b0b8891badb026d79cc54597cb2d225b54c00c5238c25d05c30e3fbeda97d2e0e1aba483a2df9f2"},{"Name":"DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305","Setup":"mode: 0\nkem_id: 32\nkdf_id: 1\naead_id: 3\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b\npkEm: 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a\nskEm: f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600\nikmR: 1ac01f181fdf9f352797655161c58b75c656a6cc2716dcb66372da835542e1df\npkRm: 4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a\nskRm: 8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb\nenc: 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a\nshared_secret: 0bbe78490412b4bbea4812666f7916932b828bba79942424abb65244930d69a7\nkey_schedule_context: 00431df6cd95e11ff49d7013563baf7f11588c75a6611e e2a4404a49306ae4cfc5b69c5718a60cc5876c358d3f7fc31ddb598503f67be58ea1e798c0bb19eb9796\nsecret: 5b9cd775e64b437a2335cf499361b2e0d5e444d5cb41a8a53336d8fe402282c6\nkey: ad2744de8e17f4ebba575b3f5f5a8fa1f69c2a07f6e7500bc60ca6e3e3ec1c91\nbase_nonce: 5c4d98150661b848853b547f\nexporter_secret: a3b010d4994890e2c6968a36f64470d3c824c8f5029942feb11e7a74b2921922","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 5c4d98150661b848853b547f\nct: 1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 5c4d98150661b848853b547e\nct: 6b53c051e4199c518de79594e1c4ab18b96f081549d45ce015be002090bb119e85285337cc95ba5f59992dc98c\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 5c4d98150661b848853b547d\nct: 71146bd6795ccc9c49ce25dda112a48f202ad220559502cef1f34271e0cb4b02b4f10ecac6f48c32f878fae86b\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 5c4d98150661b848853b547b\nct: 63357a2aa291f5a4e5f27db6baa2af8cf77427c7c1a909e0b37214dd47db122bb153495ff0b02e9e54a50dbe16\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 5c4d98150661b848853b5480\nct: 18ab939d63ddec9f6ac2b60d61d36a7375d2070c9b683861110757062c52b8880a5f6b3936da9cd6c23ef2a95c\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 5c4d98150661b848853b557f\nct: 7a4a13e9ef23978e2c520fd4d2e757514ae160cd0cd05e556ef692370ca53076214c0c40d4c728d6ed9e727a5b"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM","Setup":"mode: 0\nkem_id: 16\nkdf_id: 1\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e\npkEm: 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4\nskEm: 4995788ef4b9d6132b249ce59a77281493eb39af373d236a1fe415cb0c2d7beb\nikmR: 668b37171f1072f3cf12ea8a236a45df23fc13b82af3609ad1e354f6ef817550\npkRm: 04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0\nskRm: f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2\nenc: 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4\nshared_secret: c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8\nkey_schedule_context: 00b88d4e6d91759e65e87c470e8b9141113e9ad5f0c8ceefc1e088c82e6980500798e486f9c9c09c9b5c753ac72d6005de254c607d1b534ed11d493ae1c1d9ac85\nsecret: 2eb7b6bf138f6b5aff857414a058a3f1750054a9ba1f72c2cf0684a6f20b10e1\nkey: 868c066ef58aae6dc589b6cfdd18f97e\nbase_nonce: 4e0bc5018beba4bf004cca59\nexporter_secret: 14ad94af484a7ad3ef40e9f3be99ecc6fa9036df9d4920548424df127ee0d99f","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 4e0bc5018beba4bf004cca59\nct: 5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 4e0bc5018beba4bf004cca58\nct: fa6f037b47fc21826b610172ca9637e82d6e5801eb31cbd3748271affd4ecb06646e0329cbdf3c3cd655b28e82\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 4e0bc5018beba4bf004cca5b\nct: 895cabfac50ce6c6eb02ffe6c048bf53b7f7be9a91fc559402cbc5b8dcaeb52b2ccc93e466c28fb55fed7a7fec\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 4e0bc5018beba4bf004cca5d\nct: 8787491ee8df99bc99a246c4b3216d3d57ab5076e18fa27133f520703bc70ec999dd36ce042e44f0c3169a6a8f\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 4e0bc5018beba4bf004ccaa6\nct: 2ad71c85bf3f45c6eca301426289854b31448bcf8a8ccb1deef3ebd87f60848aa53c538c30a4dac71d619ee2cd\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 4e0bc5018beba4bf004ccb59\nct: 10f179686aa2caec1758c8e554513f16472bd0a11e2a907dde0b212cbe87d74f367f8ffe5e41cd3e9962a6afb2"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA512, AES-128-GCM","Setup":"mode: 0\nkem_id: 16\nkdf_id: 3\naead_id: 1\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 4ab11a9dd78c39668f7038f921ffc0993b368171d3ddde8031501ee1e08c4c9a\npkEm: 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580\nskEm: 2292bf14bb6e15b8c81a0f45b7a6e93e32d830e48cca702e0affcfb4d07e1b5c\nikmR: ea9ff7cc5b2705b188841c7ace169290ff312a9cb31467784ca92d7a2e6e1be8\npkRm: 04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd\nskRm: 3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38\nenc: 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580\nshared_secret: 02f584736390fc93f5b4ad039826a3fa08e9911bd1215a3db8e8791ba533cafd\nkey_schedule_context: 005b8a3617af7789ee716e7911c7e77f84cdc4cc46e60fb7e19e4059f9aeadc00585e26874d1ddde76e551a7679cd47168c466f6e1f705cc9374c192778a34fcd5ca221d77e229a9d11b654de7942d685069c633b2362ce3b3d8ea4891c9a2a87a4eb7cdb289ba5e2ecbf8cd2c8498bb4a383dc021454d70d46fcbbad1252ef4f9\nsecret: 0c7acdab61693f936c4c1256c78e7be30eebfe466812f9cc49f0b58dc970328dfc03ea359be0250a471b1635a193d2dfa8cb23c90aa2e25025b892a725353eeb\nkey: 090ca96e5f8aa02b69fac360da50ddf9\nbase_nonce: 9c995e621bf9a20c5ca45546\nexporter_secret: 4a7abb2ac43e6553f129b2c5750a7e82d149a76ed56dc342d7bca61e26d494f4855dff0d0165f27ce57756f7f16baca006539bb8e4518987ba610480ac03efa8","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 9c995e621bf9a20c5ca45546\nct: d3cf4984931484a080f74c1bb2a6782700dc1fef9abe8442e44a6f09044c88907200b332003543754eb51917ba\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 9c995e621bf9a20c5ca45547\nct: d14414555a47269dfead9fbf26abb303365e40709a4ed16eaefe1f2070f1ddeb1bdd94d9e41186f124e0acc62d\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 9c995e621bf9a20c5ca45544\nct: 9bba136cade5c4069707ba91a61932e2cbedda2d9c7bdc33515aa01dd0e0f7e9d3579bf4016dec37da4aafa800\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 9c995e621bf9a20c5ca45542\nct: a531c0655342be013bf32112951f8df1da643602f1866749519f5dcb09cc68432579de305a77e6864e862a7600\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 9c995e621bf9a20c5ca455b9\nct: be5da649469efbad0fb950366a82a73fefeda5f652ec7d3731fac6c4ffa21a7004d2ab8a04e13621bd3629547d\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 9c995e621bf9a20c5ca45446\nct: 62092672f5328a0dde095e57435edf7457ace60b26ee44c9291110ec135cb0e14b85594e4fea11247d937deb62"},{"Name":"DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305","Setup":"mode: 0\nkem_id: 16\nkdf_id: 1\naead_id: 3\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: f1f1a3bc95416871539ecb51c3a8f0cf608afb40fbbe305c0a72819d35c33f1f\npkEm: 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291\nskEm: 7550253e1147aae48839c1f8af80d2770fb7a4c763afe7d0afa7e0f42a5b3689\nikmR: 61092f3f56994dd424405899154a9918353e3e008171517ad576b900ddb275e7\npkRm: 04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a006\nskRm: a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b\nenc: 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291\nshared_secret: 806520f82ef0b03c823b7fc524b6b55a088f566b9751b89551c170f4113bd850\nkey_schedule_context: 00b738cd703db7b4106e93b4621e9a19c89c838e55964240e5d3f331aaf8b0d58b2e986ea1c671b61cf45eec134dac0bae58ec6f63e790b1400b47c33038b0269c\nsecret: fe891101629aa355aad68eff3cc5170d057eca0c7573f6575e91f9783e1d4506\nkey: a8f45490a92a3b04d1dbf6cf2c3939ad8bfc9bfcb97c04bffe116730c9dfe3fc\nbase_nonce: 726b4390ed2209809f58c693\nexporter_secret: 4f9bd9b3a8db7d7c3a5b9d44fdc1f6e37d5d77689ade5ec44a7242016e6aa205","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 726b4390ed2209809f58c693\nct: 6469c41c5c81d3aa85432531ecf6460ec945bde1eb428cb2fedf7a29f5a685b4ccb0d057f03ea2952a27bb458b\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 726b4390ed2209809f58c692\nct: f1564199f7e0e110ec9c1bcdde332177fc35c1adf6e57f8d1df24022227ffa8716862dbda2b1dc546c9d114374\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 726b4390ed2209809f58c691\nct: 39de89728bcb774269f882af8dc5369e4f3d6322d986e872b3a8d074c7c18e8549ff3f85b6d6592ff87c3f310c\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 726b4390ed2209809f58c697\nct: bc104a14fbede0cc79eeb826ea0476ce87b9c928c36e5e34dc9b6905d91473ec369a08b1a25d305dd45c6c5f80\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 726b4390ed2209809f58c66c\nct: 8f2814a2c548b3be50259713c6724009e092d37789f6856553d61df23ebc079235f710e6af3c3ca6eaba7c7c6c\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 726b4390ed2209809f58c793\nct: b45b69d419a9be7219d8c94365b89ad6951caf4576ea4774ea40e9b7047a09d6537d1aa2f7c12d6ae4b729b4d0"},{"Name":"DHKEM(P-521, HKDF-SHA512), HKDF-SHA512, AES-256-GCM","Setup":"mode: 0\nkem_id: 18\nkdf_id: 3\naead_id: 2\ninfo: 4f6465206f6e2061204772656369616e2055726e\nikmE: 7f06ab8215105fc46aceeb2e3dc5028b44364f960426eb0d8e4026c2f8b5d7e7a986688f1591abf5ab753c357a5d6f0440414b4ed4ede71317772ac98d9239f70904\npkEm: 040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0\nskEm: 014784c692da35df6ecde98ee43ac425dbdd0969c0c72b42f2e708ab9d535415a8569bdacfcc0a114c85b8e3f26acf4d68115f8c91a66178cdbd03b7bcc5291e374b\nikmR: 2ad954bbe39b7122529f7dde780bff626cd97f850d0784a432784e69d86eccaade43b6c10a8ffdb94bf943c6da479db137914ec835a7e715e36e45e29b587bab3bf1\npkRm: 0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa6837580e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f64704f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f66d2451ec64\nskRm: 01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c27196a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b2462847\nenc: 040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0\nshared_secret: 776ab421302f6eff7d7cb5cb1adaea0cd50872c71c2d63c30c4f1d5e43653336fef33b103c67e7a98add2d3b66e2fda95b5b2a667aa9dac7e59cc1d46d30e818\nkey_schedule_context: 0083a27c5b2358ab4dae1b2f5d8f57f10ccccc822a473326f543f239a70aee46347324e84e02d7651a10d08fb3dda739d22d50c53fbfa8122baacd0f9ae5913072ef45baa1f3a4b169e141feb957e48d03f28c837d8904c3d6775308c3d3faa75dd64adfa44e1a1141edf9349959b8f8e5291cbdc56f62b0ed6527d692e85b09a4\nsecret: 49fd9f53b0f93732555b2054edfdc0e3101000d75df714b98ce5aa295a37f1b18dfa86a1c37286d805d3ea09a20b72f93c21e83955a1f01eb7c5eead563d21e7\nkey: 751e346ce8f0ddb2305c8a2a85c70d5cf559c53093656be636b9406d4d7d1b70\nbase_nonce: 55ff7a7d739c69f44b25447b\nexporter_secret: e4ff9dfbc732a2b9c75823763c5ccc954a2c0648fc6de80a58581252d0ee3215388a4455e69086b50b87eb28c169a52f42e71de4ca61c920e7bd24c95cc3f992","Encryptions":"sequence number: 0\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d30\nnonce: 55ff7a7d739c69f44b25447b\nct: 170f8beddfe949b75ef9c387e201baf4132fa7374593dfafa90768788b7b2b200aafcc6d80ea4c795a7c5b841a\n\nsequence number: 1\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d31\nnonce: 55ff7a7d739c69f44b25447a\nct: d9ee248e220ca24ac00bbbe7e221a832e4f7fa64c4fbab3945b6f3af0c5ecd5e16815b328be4954a05fd352256\n\nsequence number: 2\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d32\nnonce: 55ff7a7d739c69f44b254479\nct: 142cf1e02d1f58d9285f2af7dcfa44f7c3f2d15c73d460c48c6e0e506a3144bae35284e7e221105b61d24e1c7a\n\nsequence number: 4\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d34\nnonce: 55ff7a7d739c69f44b25447f\nct: 3bb3a5a07100e5a12805327bf3b152df728b1c1be75a9fd2cb2bf5eac0cca1fb80addb37eb2a32938c7268e3e5\n\nsequence number: 255\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323535\nnonce: 55ff7a7d739c69f44b254484\nct: 4f268d0930f8d50b8fd9d0f26657ba25b5cb08b308c92e33382f369c768b558e113ac95a4c70dd60909ad1adc7\n\nsequence number: 256\npt: 4265617574792069732074727574682c20747275746820626561757479\naad: 436f756e742d323536\nnonce: 55ff7a7d739c69f44b25457b\nct: dbbfc44ae037864e75f136e8b4b4123351d480e6619ae0e0ae437f036f2f8f1ef677686323977a1ccbb4b4f16a"}]
--- /dev/null
+[
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 1,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234",
+ "ikmR": "6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037",
+ "skRm": "4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8",
+ "pkRm": "3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d",
+ "enc": "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431",
+ "encryptions": "dcabb32ad8e8acea785275323395abd0",
+ "exports": "45db490fc51c86ba46cca1217f66a75e"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 1,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "2cd7c601cefb3d42a62b04b7a9041494c06c7843818e0ce28a8f704ae7ab20f9",
+ "ikmR": "dac33b0e9db1b59dbbea58d59a14e7b5896e9bdf98fad6891e99d1686492b9ee",
+ "skRm": "497b4502664cfea5d5af0b39934dac72242a74f8480451e1aee7d6a53320333d",
+ "pkRm": "430f4b9859665145a6b1ba274024487bd66f03a2dd577d7753c68d7d7d00c00c",
+ "enc": "6c93e09869df3402d7bf231bf540fadd35cd56be14f97178f0954db94b7fc256",
+ "encryptions": "1702e73e1e71705faa8241022af1deea",
+ "exports": "5cb678bf1c52afbd9afb58b8f7c1ced3"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 1,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b",
+ "ikmR": "1ac01f181fdf9f352797655161c58b75c656a6cc2716dcb66372da835542e1df",
+ "skRm": "8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb",
+ "pkRm": "4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a",
+ "enc": "1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a",
+ "encryptions": "225fb3d35da3bb25e4371bcee4273502",
+ "exports": "54e2189c04100b583c84452f94eb9a4a"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 1,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "55bc245ee4efda25d38f2d54d5bb6665291b99f8108a8c4b686c2b14893ea5d9",
+ "ikmR": "683ae0da1d22181e74ed2e503ebf82840deb1d5e872cade20f4b458d99783e31",
+ "skRm": "33d196c830a12f9ac65d6e565a590d80f04ee9b19c83c87f2c170d972a812848",
+ "pkRm": "194141ca6c3c3beb4792cd97ba0ea1faff09d98435012345766ee33aae2d7664",
+ "enc": "e5e8f9bfff6c2f29791fc351d2c25ce1299aa5eaca78a757c0b4fb4bcd830918",
+ "exports": "3fe376e3f9c349bc5eae67bbce867a16"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 3,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "895221ae20f39cbf46871d6ea162d44b84dd7ba9cc7a3c80f16d6ea4242cd6d4",
+ "ikmR": "59a9b44375a297d452fc18e5bba1a64dec709f23109486fce2d3a5428ed2000a",
+ "skRm": "ddfbb71d7ea8ebd98fa9cc211aa7b535d258fe9ab4a08bc9896af270e35aad35",
+ "pkRm": "adf16c696b87995879b27d470d37212f38a58bfe7f84e6d50db638b8f2c22340",
+ "enc": "8998da4c3d6ade83c53e861a022c046db909f1c31107196ab4c2f4dd37e1a949",
+ "encryptions": "19a0d0fb001f83e7606948507842f913",
+ "exports": "e5d853af841b92602804e7a40c1f2487"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 3,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "e72b39232ee9ef9f6537a72afe28f551dbe632006aa1b300a00518883a3f2dc1",
+ "ikmR": "a0484936abc95d587acf7034156229f9970e9dfa76773754e40fb30e53c9de16",
+ "skRm": "bdd8943c1e60191f3ea4e69fc4f322aa1086db9650f1f952fdce88395a4bd1af",
+ "pkRm": "aa7bddcf5ca0b2c0cf760b5dffc62740a8e761ec572032a809bebc87aaf7575e",
+ "enc": "c12ba9fb91d7ebb03057d8bea4398688dcc1d1d1ff3b97f09b96b9bf89bd1e4a",
+ "encryptions": "20402e520fdbfee76b2b0af73d810deb",
+ "exports": "80b7f603f0966ca059dd5e8a7cede735"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 3,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "636d1237a5ae674c24caa0c32a980d3218d84f916ba31e16699892d27103a2a9",
+ "ikmR": "969bb169aa9c24a501ee9d962e96c310226d427fb6eb3fc579d9882dbc708315",
+ "skRm": "fad15f488c09c167bd18d8f48f282e30d944d624c5676742ad820119de44ea91",
+ "pkRm": "06aa193a5612d89a1935c33f1fda3109fcdf4b867da4c4507879f184340b0e0e",
+ "enc": "1d38fc578d4209ea0ef3ee5f1128ac4876a9549d74dc2d2f46e75942a6188244",
+ "encryptions": "c03e64ef58b22065f04be776d77e160c",
+ "exports": "fa84b4458d580b5069a1be60b4785eac"
+ },
+ {
+ "mode": 0,
+ "kem_id": 32,
+ "kdf_id": 3,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "3cfbc97dece2c497126df8909efbdd3d56b3bbe97ddf6555c99a04ff4402474c",
+ "ikmR": "dff9a966e02b161472f167c0d4252d400069449e62384beb78111cb596220921",
+ "skRm": "7596739457c72bbd6758c7021cfcb4d2fcd677d1232896b8f00da223c5519c36",
+ "pkRm": "9a83674c1bc12909fd59635ba1445592b82a7c01d4dad3ffc8f3975e76c43732",
+ "enc": "444fbbf83d64fef654dfb2a17997d82ca37cd8aeb8094371da33afb95e0c5b0e",
+ "exports": "7557bdf93eadf06e3682fce3d765277f"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 1,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e",
+ "ikmR": "668b37171f1072f3cf12ea8a236a45df23fc13b82af3609ad1e354f6ef817550",
+ "skRm": "f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2",
+ "pkRm": "04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0",
+ "enc": "04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4",
+ "encryptions": "fcb852ae6a1e19e874fbd18a199df3e4",
+ "exports": "655be1f8b189a6b103528ac6d28d3109"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 1,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "a90d3417c3da9cb6c6ae19b4b5dd6cc9529a4cc24efb7ae0ace1f31887a8cd6c",
+ "ikmR": "a0ce15d49e28bd47a18a97e147582d814b08cbe00109fed5ec27d1b4e9f6f5e3",
+ "skRm": "317f915db7bc629c48fe765587897e01e282d3e8445f79f27f65d031a88082b2",
+ "pkRm": "04abc7e49a4c6b3566d77d0304addc6ed0e98512ffccf505e6a8e3eb25c685136f853148544876de76c0f2ef99cdc3a05ccf5ded7860c7c021238f9e2073d2356c",
+ "enc": "04c06b4f6bebc7bb495cb797ab753f911aff80aefb86fd8b6fcc35525f3ab5f03e0b21bd31a86c6048af3cb2d98e0d3bf01da5cc4c39ff5370d331a4f1f7d5a4e0",
+ "encryptions": "8d3263541fc1695b6e88ff3a1208577c",
+ "exports": "038af0baa5ce3c4c5f371c3823b15217"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 1,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "f1f1a3bc95416871539ecb51c3a8f0cf608afb40fbbe305c0a72819d35c33f1f",
+ "ikmR": "61092f3f56994dd424405899154a9918353e3e008171517ad576b900ddb275e7",
+ "skRm": "a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b",
+ "pkRm": "04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a006",
+ "enc": "04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291",
+ "encryptions": "702cdecae9ba5c571c8b00ad1f313dbf",
+ "exports": "2e0951156f1e7718a81be3004d606800"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 1,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "3800bb050bb4882791fc6b2361d7adc2543e4e0abbac367cf00a0c4251844350",
+ "ikmR": "c6638d8079a235ea4054885355a7caefee67151c6ff2a04f4ba26d099c3a8b02",
+ "skRm": "62c3868357a464f8461d03aa0182c7cebcde841036aea7230ddc7339f1088346",
+ "pkRm": "046c6bb9e1976402c692fef72552f4aaeedd83a5e5079de3d7ae732da0f397b15921fb9c52c9866affc8e29c0271a35937023a9245982ec18bab1eb157cf16fc33",
+ "enc": "04d804370b7e24b94749eb1dc8df6d4d4a5d75f9effad01739ebcad5c54a40d57aaa8b4190fc124dbde2e4f1e1d1b012a3bc4038157dc29b55533a932306d8d38d",
+ "exports": "a6d39296bc2704db6194b7d6180ede8a"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 3,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "4ab11a9dd78c39668f7038f921ffc0993b368171d3ddde8031501ee1e08c4c9a",
+ "ikmR": "ea9ff7cc5b2705b188841c7ace169290ff312a9cb31467784ca92d7a2e6e1be8",
+ "skRm": "3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38",
+ "pkRm": "04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd",
+ "enc": "0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580",
+ "encryptions": "3d670fc7760ce5b208454bb678fbc1dd",
+ "exports": "0a3e30b572dafc58b998cd51959924be"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 3,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "0c4b7c8090d9995e298d6fd61c7a0a66bb765a12219af1aacfaac99b4deaf8ad",
+ "ikmR": "a2f6e7c4d9e108e03be268a64fe73e11a320963c85375a30bfc9ec4a214c6a55",
+ "skRm": "9648e8711e9b6cb12dc19abf9da350cf61c3669c017b1db17bb36913b54a051d",
+ "pkRm": "0400f209b1bf3b35b405d750ef577d0b2dc81784005d1c67ff4f6d2860d7640ca379e22ac7fa105d94bc195758f4dfc0b82252098a8350c1bfeda8275ce4dd4262",
+ "enc": "0404dc39344526dbfa728afba96986d575811b5af199c11f821a0e603a4d191b25544a402f25364964b2c129cb417b3c1dab4dfc0854f3084e843f731654392726",
+ "encryptions": "9da1683aade69d882aa094aa57201481",
+ "exports": "80ab8f941a71d59f566e5032c6e2c675"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 3,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "02bd2bdbb430c0300cea89b37ada706206a9a74e488162671d1ff68b24deeb5f",
+ "ikmR": "8d283ea65b27585a331687855ab0836a01191d92ab689374f3f8d655e702d82f",
+ "skRm": "ebedc3ca088ad03dfbbfcd43f438c4bb5486376b8ccaea0dc25fc64b2f7fc0da",
+ "pkRm": "048fed808e948d46d95f778bd45236ce0c464567a1dc6f148ba71dc5aeff2ad52a43c71851b99a2cdbf1dad68d00baad45007e0af443ff80ad1b55322c658b7372",
+ "enc": "044415d6537c2e9dd4c8b73f2868b5b9e7e8e3d836990dc2fd5b466d1324c88f2df8436bac7aa2e6ebbfd13bd09eaaa7c57c7495643bacba2121dca2f2040e1c5f",
+ "encryptions": "f025dca38d668cee68e7c434e1b98f9f",
+ "exports": "2efbb7ade3f87133810f507fdd73f874"
+ },
+ {
+ "mode": 0,
+ "kem_id": 16,
+ "kdf_id": 3,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "497efeca99592461588394f7e9496129ed89e62b58204e076d1b7141e999abda",
+ "ikmR": "49b7cbfc1756e8ae010dc80330108f5be91268b3636f3e547dbc714d6bcd3d16",
+ "skRm": "9d34abe85f6da91b286fbbcfbd12c64402de3d7f63819e6c613037746b4eae6b",
+ "pkRm": "0453a4d1a4333b291e32d50a77ac9157bbc946059941cf9ed5784c15adbc7ad8fe6bf34a504ed81fd9bc1b6bb066a037da30fccd6c0b42d72bf37b9fef43c8e498",
+ "enc": "04f910248e120076be2a4c93428ac0c8a6b89621cfef19f0f9e113d835cf39d5feabbf6d26444ebbb49c991ec22338ade3a5edff35a929be67c4e5f33dcff96706",
+ "exports": "6df17307eeb20a9180cff75ea183dd60"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 1,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "5040af7a10269b11f78bb884812ad20041866db8bbd749a6a69e3f33e54da7164598f005bce09a9fe190e29c2f42df9e9e3aad040fccc625ddbd7aa99063fc594f40",
+ "ikmR": "39a28dc317c3e48b908948f99d608059f882d3d09c0541824bc25f94e6dee7aa0df1c644296b06fbb76e84aef5008f8a908e08fbabadf70658538d74753a85f8856a",
+ "skRm": "009227b4b91cf1eb6eecb6c0c0bae93a272d24e11c63bd4c34a581c49f9c3ca01c16bbd32a0a1fac22784f2ae985c85f183baad103b2d02aee787179dfc1a94fea11",
+ "pkRm": "0400b81073b1612cf7fdb6db07b35cf4bc17bda5854f3d270ecd9ea99f6c07b46795b8014b66c523ceed6f4829c18bc3886c891b63fa902500ce3ddeb1fbec7e608ac70050b76a0a7fc081dbf1cb30b005981113e635eb501a973aba662d7f16fcc12897dd752d657d37774bb16197c0d9724eecc1ed65349fb6ac1f280749e7669766f8cd",
+ "enc": "0400bec215e31718cd2eff5ba61d55d062d723527ec2029d7679a9c867d5c68219c9b217a9d7f78562dc0af3242fef35d1d6f4a28ee75f0d4b31bc918937b559b70762004c4fd6ad7373db7e31da8735fbd6171bbdcfa770211420682c760a40a482cc24f4125edbea9cb31fe71d5d796cfe788dc408857697a52fef711fb921fa7c385218",
+ "encryptions": "94209973d36203eef2e56d155ef241d5",
+ "exports": "31f25ea5e192561bce5f2c2822a9432c"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 1,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "9953fbd633be69d984fc4fffc4d7749f007dbf97102d36a647a8108b0bb7c609e826b026aec1cd47b93fc5acb7518fa455ed38d0c29e900c56990635612fd3d220d2",
+ "ikmR": "17320bc93d9bc1d422ba0c705bf693e9a51a855d6e09c11bddea5687adc1a1122ec81384dc7e47959cae01c420a69e8e39337d9ebf9a9b2f3905cb76a35b0693ac34",
+ "skRm": "01a27e65890d64a121cfe59b41484b63fd1213c989c00e05a049ac4ede1f5caeec52bf43a59bdc36731cb6f8a0b7d7724b047ff52803c421ee99d61d4ea2e569c825",
+ "pkRm": "0400eb4010ca82412c044b52bdc218625c4ea797e061236206843e318882b3c1642e7e14e7cc1b4b171a433075ac0c8563043829eee51059a8b68197c8a7f6922465650075f40b6f440fdf525e2512b0c2023709294d912d8c68f94140390bff228097ce2d5f89b2b21f50d4c0892cfb955c380293962d5fe72060913870b61adc8b111953",
+ "enc": "0401c1cf49cafa9e26e24a9e20d7fa44a50a4e88d27236ef17358e79f3615a97f825899a985b3edb5195cad24a4fb64828701e81fbfd9a7ef673efde508e789509bd7c00fd5bfe053377bbee22e40ae5d64aa6fb47b314b5ab7d71b652db9259962dce742317d54084f0cf62a4b7e3f3caa9e6afb8efd6bf1eb8a2e13a7e73ec9213070d68",
+ "encryptions": "69d16fa7c814cd8be9aa2122fda8768f",
+ "exports": "d295fad3aef8be1f89d785800f83a30b"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 1,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "566568b6cbfd1c6c06d1b0a2dc22d4e4965858bf3d54bf6cba5c018be0fad7a5cd9237937800f3cb57f10fa5691faeecab1685aa6da9b667469224a0989ff82b822b",
+ "ikmR": "f9f594556282cfe3eb30958ca2ef90ecd2a6ffd2661d41eb39ba184f3dae9f914aad297dd80cc763cb6525437a61ceae448aeeb304de137dc0f28dd007f0d592e137",
+ "skRm": "0168c8bf969b30bd949e154bf2db1964535e3f230f6604545bc9a33e9cd80fb17f4002170a9c91d55d7dd21db48e687cea83083498768cc008c6adf1e0ca08a309bd",
+ "pkRm": "040086b1a785a52af34a9a830332999896e99c5df0007a2ec3243ee3676ba040e60fde21bacf8e5f8db26b5acd42a2c81160286d54a2f124ca8816ac697993727431e50002aa5f5ebe70d88ff56445ade400fb979b466c9046123bbf5be72db9d90d1cde0bb7c217cff8ea0484445150eaf60170b039f54a5f6baeb7288bc62b1dedb59a1b",
+ "enc": "0401f828650ec526a647386324a31dadf75b54550b06707ae3e1fb83874b2633c935bb862bc4f07791ccfafbb08a1f00e18c531a34fec76f2cf3d581e7915fa40bbc3b010ab7c3d9162ea69928e71640ecff08b97f4fa9e8c66dfe563a13bf561cee7635563f91d387e2a38ee674ea28b24c633a988d1a08968b455e96307c64bda3f094b7",
+ "encryptions": "586d5a92612828afbd7fdcea96006892",
+ "exports": "a70389af65de4452a3f3147b66bd5c73"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 1,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "5dfb76f8b4708970acb4a6efa35ec4f2cebd61a3276a711c2fa42ef0bc9c191ea9dac7c0ac907336d830cea4a8394ab69e9171f344c4817309f93170cb34914987a5",
+ "ikmR": "9fd2aad24a653787f53df4a0d514c6d19610ca803298d7812bc0460b76c21da99315ebfec2343b4848d34ce526f0d39ce5a8dfddd9544e1c4d4b9a62f4191d096b42",
+ "skRm": "01ca47cf2f6f36fef46a01a46b393c30672224dd566aa3dd07a229519c49632c83d800e66149c3a7a07b840060549accd0d480ec5c71d2a975f88f6aa2fc0810b393",
+ "pkRm": "040143b7db23907d3ae1c43ef4882a6cdb142ca05a21c2475985c199807dd143e898136c65faf1ca1b6c6c2e8a92d67a0ab9c24f8c5cff7610cb942a73eb2ec4217c26018d67621cc78a60ec4bd1e23f90eb772adba2cf5a566020ee651f017b280a155c016679bd7e7ebad49e28e7ab679f66765f4ef34eae6b38a99f31bc73ea0f0d694d",
+ "enc": "040073dda7343ce32926c028c3be28508cccb751e2d4c6187bcc4e9b1de82d3d70c5702c6c866a920d9d9a574f5a4d4a0102db76207d5b3b77da16bb57486c5cc2a95f006b5d2e15efb24e297bdf8f2b6d7b25bf226d1b6efca47627b484d2942c14df6fe018d82ab9fb7306370c248864ea48fe5ca94934993517aacaa3b6bca8f92efc84",
+ "exports": "d8fa94ac5e6829caf5ab4cdd1e05f5e1"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 3,
+ "aead_id": 1,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "018b6bb1b8bbcefbd91e66db4e1300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "ikmR": "7bf9fd92611f2ff4e6c2ab4dd636a320e0397d6a93d014277b025a7533684c3255a02aa1f2a142be5391eebfc60a6a9c729b79c2428b8d78fa36497b1e89e446d402",
+ "skRm": "019db24a3e8b1f383436cd06997dd864eb091418ff561e3876cee2e4762a0cc0b69688af9a7a4963c90d394b2be579144af97d4933c0e6c2c2d13e7505ea51a06b0d",
+ "pkRm": "0401e06b350786c48a60dfc50eed324b58ecafc4efba26242c46c14274bd97f0989487a6fae0626188fea971ae1cb53f5d0e87188c1c62af92254f17138bbcebf5acd0018e574ee1d695813ce9dc45b404d2cf9c04f27627c4c55da1f936d813fd39435d0713d4a3cdc5409954a1180eb2672bdfc4e0e79c04eda89f857f625e058742a1c8",
+ "enc": "0400ac8d1611948105f23cf5e6842b07bd39b352d9d1e7bff2c93ac063731d6372e2661eff2afce604d4a679b49195f15e4fa228432aed971f2d46c1beb51fb3e5812501fe199c3d94c1b199393642500443dd82ce1c01701a1279cc3d74e29773030e26a70d3512f761e1eb0d7882209599eb9acd295f5939311c55e737f11c19988878d6",
+ "encryptions": "207972885962115e69daaa3bc5015151",
+ "exports": "8e9c577501320d86ee84407840188f5f"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 3,
+ "aead_id": 2,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "7f06ab8215105fc46aceeb2e3dc5028b44364f960426eb0d8e4026c2f8b5d7e7a986688f1591abf5ab753c357a5d6f0440414b4ed4ede71317772ac98d9239f70904",
+ "ikmR": "2ad954bbe39b7122529f7dde780bff626cd97f850d0784a432784e69d86eccaade43b6c10a8ffdb94bf943c6da479db137914ec835a7e715e36e45e29b587bab3bf1",
+ "skRm": "01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c27196a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b2462847",
+ "pkRm": "0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa6837580e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f64704f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f66d2451ec64",
+ "enc": "040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab8900aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f2731ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de12051f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5739d2f29b0",
+ "encryptions": "31769e36bcca13288177eb1c92f616ae",
+ "exports": "fbffd93db9f000f51cf8ab4c1127fbda"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 3,
+ "aead_id": 3,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "f9d540fde009bb1e5e71617c122a079862306b97144c8c4dca45ef6605c2ec9c43527c150800f5608a7e4cff771226579e7c776fb3def4e22e68e9fdc92340e94b6e",
+ "ikmR": "5273f7762dea7a2408333dbf8db9f6ef2ac4c475ad9e81a3b0b8c8805304adf5c876105d8703b42117ad8ee350df881e3d52926aafcb5c90f649faf94be81952c78a",
+ "skRm": "015b59f17366a1d4442e5b92d883a8f35fe8d88fea0e5bac6dfac7153c78fd0c6248c618b083899a7d62ba6e00e8a22cdde628dd5399b9a3377bb898792ff6f54ab9",
+ "pkRm": "040084698a47358f06a92926ee826a6784341285ee45f4b8269de271a8c6f03d5e8e24f628de13f5c37377b7cabfbd67bc98f9e8e758dfbee128b2fe752cd32f0f3ccd0061baec1ed7c6b52b7558bc120f783e5999c8952242d9a20baf421ccfc2a2b87c42d7b5b806fea6d518d5e9cd7bfd6c85beb5adeb72da41ac3d4f27bba83cff24d7",
+ "enc": "0400edc201c9b32988897a7f7b19104ebb54fc749faa41a67e9931e87ec30677194898074afb9a5f40a97df2972368a0c594e5b60e90d1ff83e9e35f8ff3ad200fd6d70028b5645debe9f1f335dbc1225c066218e85cf82a05fbe361fa477740b906cb3083076e4d17232513d102627597d38e354762cf05b3bd0f33dc4d0fb78531afd3fd",
+ "encryptions": "aa69356025f552372770ef126fa2e59a",
+ "exports": "1fcffb5d8bc1d825daf904a0c6f4a4d3"
+ },
+ {
+ "mode": 0,
+ "kem_id": 18,
+ "kdf_id": 3,
+ "aead_id": 65535,
+ "info": "4f6465206f6e2061204772656369616e2055726e",
+ "ikmE": "3018d74c67d0c61b5e4075190621fc192996e928b8859f45b3ad2399af8599df69c34b7a3eefeda7ee49ae73d4579300b85dde1654c0dfc3a3f78143d239a628cf72",
+ "ikmR": "a243eff510b99140034c72587e9f131809b9bce03a9da3da458771297f535cede0f48167200bf49ac123b52adfd789cf0adfd5cded6be2f146aeb00c34d4e6d234fc",
+ "skRm": "0045fe00b1d55eb64182d334e301e9ac553d6dbafbf69935e65f5bf89c761b9188c0e4d50a0167de6b98af7bebd05b2627f45f5fca84690cd86a61ba5a612870cf53",
+ "pkRm": "0401635b3074ad37b752696d5ca311da9cc790a899116030e4c71b83edd06ced92fdd238f6c921132852f20e6a2cbcf2659739232f4a69390f2b14d80667bcf9b71983000a919d29366554f53107a6c4cc7f8b24fa2de97b42433610cbd236d5a2c668e991ff4c4383e9fe0a9e7858fc39064e31fca1964e809a2f898c32fba46ce33575b8",
+ "enc": "0400932d9ff83ca4b799968bda0dd9dac4d02c9232cdcf133db7c53cfbf3d80a299fd99bc42da38bb78f57976bdb69988819b6e2924fadacdad8c05052997cf50b29110139f000af5b2c599b05fc63537d60a8384ca984821f8cd12621577a974ebadaf98bfdad6d1643dd4316062d7c0bda5ba0f0a2719992e993af615568abf19a256993",
+ "exports": "29c0f6150908f6e0d979172f23f1d57b"
+ }
+]
// with a specific ECH config known to a client.
type EncryptedClientHelloKey struct {
// Config should be a marshalled ECHConfig associated with PrivateKey. This
- // must match the config provided to clients byte-for-byte. The config
- // should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
- // HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
- // AES-128-GCM (0x0001), AES-256-GCM (0x0002), ChaCha20Poly1305 (0x0003).
+ // must match the config provided to clients byte-for-byte. The config must
+ // use as KEM one of
+ //
+ // - DHKEM(P-256, HKDF-SHA256) (0x0010)
+ // - DHKEM(P-384, HKDF-SHA384) (0x0011)
+ // - DHKEM(P-521, HKDF-SHA512) (0x0012)
+ // - DHKEM(X25519, HKDF-SHA256) (0x0020)
+ //
+ // and as KDF one of
+ //
+ // - HKDF-SHA256 (0x0001)
+ // - HKDF-SHA384 (0x0002)
+ // - HKDF-SHA512 (0x0003)
+ //
+ // and as AEAD one of
+ //
+ // - AES-128-GCM (0x0001)
+ // - AES-256-GCM (0x0002)
+ // - ChaCha20Poly1305 (0x0003)
+ //
Config []byte
- // PrivateKey should be a marshalled private key. Currently, we expect
- // this to be the output of [ecdh.PrivateKey.Bytes].
+ // PrivateKey should be a marshalled private key, in the format expected by
+ // HPKE's DeserializePrivateKey (see RFC 9180), for the KEM used in Config.
PrivateKey []byte
// SendAsRetry indicates if Config should be sent as part of the list of
// retry configs when ECH is requested by the client but rejected by the
"crypto/internal/hpke"
"errors"
"fmt"
- "slices"
"strings"
"golang.org/x/crypto/cryptobyte"
)
-// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
-// We need this so that when we insert them into ECHConfigs the ordering
-// is stable.
-var sortedSupportedAEADs []uint16
-
-func init() {
- for aeadID := range hpke.SupportedAEADs {
- sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID)
- }
- slices.Sort(sortedSupportedAEADs)
-}
-
type echCipher struct {
KDFID uint16
AEADID uint16
return configs, nil
}
-func pickECHConfig(list []echConfig) *echConfig {
+func pickECHConfig(list []echConfig) (*echConfig, hpke.KEMSender, hpke.KDF, hpke.AEAD) {
for _, ec := range list {
- if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
- continue
- }
- var validSCS bool
- for _, cs := range ec.SymmetricCipherSuite {
- if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
- continue
- }
- if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
- continue
- }
- validSCS = true
- break
- }
- if !validSCS {
- continue
- }
if !validDNSName(string(ec.PublicName)) {
continue
}
if unsupportedExt {
continue
}
- return &ec
- }
- return nil
-}
-
-func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
- for _, s := range suites {
- // NOTE: all of the supported AEADs and KDFs are fine, rather than
- // imposing some sort of preference here, we just pick the first valid
- // suite.
- if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
+ s, err := hpke.NewKEMSender(ec.KemID, ec.PublicKey)
+ if err != nil {
continue
}
- if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
- continue
+ for _, cs := range ec.SymmetricCipherSuite {
+ // All of the supported AEADs and KDFs are fine, rather than
+ // imposing some sort of preference here, we just pick the first
+ // valid suite.
+ kdf, err := hpke.NewKDF(cs.KDFID)
+ if err != nil {
+ continue
+ }
+ aead, err := hpke.NewAEAD(cs.AEADID)
+ if err != nil {
+ continue
+ }
+ return &ec, s, kdf, aead
}
- return s, nil
}
- return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
+ return nil, nil, nil, nil
}
func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
skip, config, err := parseECHConfig(echKey.Config)
if err != nil || skip {
c.sendAlert(alertInternalError)
- return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err)
+ return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config: %s", err)
}
if skip {
continue
}
- echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
+ echPriv, err := hpke.NewKEMRecipient(config.KemID, echKey.PrivateKey)
+ if err != nil {
+ c.sendAlert(alertInternalError)
+ return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey PrivateKey: %s", err)
+ }
+ kdf, err := hpke.NewKDF(echCiphersuite.KDFID)
+ if err != nil {
+ c.sendAlert(alertInternalError)
+ return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config KDF: %s", err)
+ }
+ aead, err := hpke.NewAEAD(echCiphersuite.AEADID)
if err != nil {
c.sendAlert(alertInternalError)
- return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err)
+ return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKey Config AEAD: %s", err)
}
info := append([]byte("tls ech\x00"), echKey.Config...)
- hpkeContext, err := hpke.SetupRecipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
+ hpkeContext, err := hpke.NewRecipient(encap, echPriv, kdf, aead, info)
if err != nil {
// attempt next trial decryption
continue
if err != nil {
t.Fatal(err)
}
- config := pickECHConfig(configs)
+ config, _, _, _ := pickECHConfig(configs)
if config != nil {
t.Fatal("pickECHConfig picked an invalid config")
}
if err != nil {
return nil, nil, nil, err
}
- echConfig := pickECHConfig(echConfigs)
+ echConfig, echPK, kdf, aead := pickECHConfig(echConfigs)
if echConfig == nil {
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
}
- ech = &echClientContext{config: echConfig}
+ ech = &echClientContext{config: echConfig, kdfID: kdf.ID(), aeadID: aead.ID()}
hello.encryptedClientHello = []byte{1} // indicate inner hello
// We need to explicitly set these 1.2 fields to nil, as we do not
// marshal them when encoding the inner hello, otherwise transcripts
hello.secureRenegotiationSupported = false
hello.extendedMasterSecret = false
- echPK, err := hpke.ParseHPKEPublicKey(ech.config.KemID, ech.config.PublicKey)
- if err != nil {
- return nil, nil, nil, err
- }
- suite, err := pickECHCipherSuite(ech.config.SymmetricCipherSuite)
- if err != nil {
- return nil, nil, nil, err
- }
- ech.kdfID, ech.aeadID = suite.KDFID, suite.AEADID
info := append([]byte("tls ech\x00"), ech.config.raw...)
- ech.encapsulatedKey, ech.hpkeContext, err = hpke.SetupSender(ech.config.KemID, suite.KDFID, suite.AEADID, echPK, info)
+ ech.encapsulatedKey, ech.hpkeContext, err = hpke.NewSender(echPK, kdf, aead, info)
if err != nil {
return nil, nil, nil, err
}
"crypto/ecdh"
"crypto/ecdsa"
"crypto/elliptic"
- "crypto/internal/hpke"
"crypto/rand"
"crypto/tls/internal/fips140tls"
"crypto/x509"
builder.AddUint16(extensionEncryptedClientHello)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddUint8(id)
- builder.AddUint16(hpke.DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
+ builder.AddUint16(0x0020 /* DHKEM(X25519, HKDF-SHA256) */)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(pubKey)
})
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
- for _, aeadID := range sortedSupportedAEADs {
- builder.AddUint16(hpke.KDF_HKDF_SHA256) // The only KDF we support
- builder.AddUint16(aeadID)
- }
+ builder.AddUint16(0x0001 /* HKDF-SHA256 */)
+ builder.AddUint16(0x0001 /* AES-128-GCM */)
})
builder.AddUint8(maxNameLen)
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {