Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion hpke/aead.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func (c *encdecContext) Export(exporterContext []byte, length uint) []byte {
if length > maxLength {
panic(fmt.Errorf("output length must be lesser than %v bytes", maxLength))
}
return c.suite.labeledExpand(c.exporterSecret, []byte("sec"),
if c.suite.kdfID.IsTwoStage() {
return c.suite.labeledExpand(c.exporterSecret, []byte("sec"),
exporterContext, uint16(length))
}
return c.suite.labeledDerive(c.exporterSecret, []byte("sec"),
exporterContext, uint16(length))
}

Expand Down
101 changes: 95 additions & 6 deletions hpke/algs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ import (

"github.com/cloudflare/circl/dh/x25519"
"github.com/cloudflare/circl/dh/x448"
"github.com/cloudflare/circl/internal/sha3"
"github.com/cloudflare/circl/kem"
"github.com/cloudflare/circl/kem/kyber/kyber768"
"github.com/cloudflare/circl/kem/mlkem/mlkem1024"
"github.com/cloudflare/circl/kem/mlkem/mlkem512"
"github.com/cloudflare/circl/kem/mlkem/mlkem768"
"github.com/cloudflare/circl/kem/xwing"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
Expand All @@ -41,6 +45,13 @@ const (
KEM_X25519_KYBER768_DRAFT00 KEM = 0x30
// KEM_XWING is a hybrid KEM using X25519 and ML-KEM-768.
KEM_XWING KEM = 0x647a

KEM_MLKEM512 KEM = 0x0040
KEM_MLKEM768 KEM = 0x0041
KEM_MLKEM1024 KEM = 0x0042

KEM_QSF_P256_MLKEM768 = 0x0050
KEM_QSF_P384_MLKEM1024 = 0x0052
)

// IsValid returns true if the KEM identifier is supported by the HPKE package.
Expand All @@ -52,7 +63,10 @@ func (k KEM) IsValid() bool {
KEM_X25519_HKDF_SHA256,
KEM_X448_HKDF_SHA512,
KEM_X25519_KYBER768_DRAFT00,
KEM_XWING:
KEM_XWING,
KEM_MLKEM512,
KEM_MLKEM768,
KEM_MLKEM1024:
return true
default:
return false
Expand All @@ -77,6 +91,12 @@ func (k KEM) Scheme() kem.Scheme {
return hybridkemX25519Kyber768
case KEM_XWING:
return kemXwing
case KEM_MLKEM512:
return kemMLKEM512
case KEM_MLKEM768:
return kemMLKEM768
case KEM_MLKEM1024:
return kemMLKEM1024
default:
panic(ErrInvalidKEM)
}
Expand All @@ -86,27 +106,57 @@ type KDF uint16

//nolint:golint,stylecheck
const (
// KDF_HKDF_SHA256 is a KDF using HKDF with SHA-256.
// KDF_HKDF_SHA256 is a two-stage KDF using HKDF with SHA-256.
KDF_HKDF_SHA256 KDF = 0x01
// KDF_HKDF_SHA384 is a KDF using HKDF with SHA-384.
// KDF_HKDF_SHA384 is a two-stage KDF using HKDF with SHA-384.
KDF_HKDF_SHA384 KDF = 0x02
// KDF_HKDF_SHA512 is a KDF using HKDF with SHA-512.
// KDF_HKDF_SHA512 is a two-stage KDF using HKDF with SHA-512.
KDF_HKDF_SHA512 KDF = 0x03

// KDF_SHAKE128 is a one-stage KDF using SHAKE-128.
KDF_SHAKE128 = 0x10
// KDF_SHAKE256 is a one-stage KDF using SHAKE-256.
KDF_SHAKE256 = 0x11
// KDF_TurboSHAKE128 is a one-stage KDF using TurboSHAKE-128.
KDF_TurboSHAKE128 = 0x12
// KDF_TurboSHAKE256 is a one-stage KDF using TurboSHAKE-256.
KDF_TurboSHAKE256 = 0x13
)

func (k KDF) IsValid() bool {
func (k KDF) IsTwoStage() bool {
switch k {
case KDF_HKDF_SHA256,
KDF_HKDF_SHA384,
KDF_HKDF_SHA512:
return true
case KDF_SHAKE128,
KDF_TurboSHAKE128,
KDF_SHAKE256,
KDF_TurboSHAKE256:
return false
default:
panic(ErrInvalidKDF)
}
}

func (k KDF) IsValid() bool {
switch k {
case KDF_HKDF_SHA256,
KDF_HKDF_SHA384,
KDF_HKDF_SHA512,
KDF_SHAKE128,
KDF_TurboSHAKE128,
KDF_SHAKE256,
KDF_TurboSHAKE256:
return true
default:
return false
}
}

// ExtractSize returns the size (in bytes) of the pseudorandom key produced
// by KDF.Extract.
// by KDF.Extract() for a two-stage KDF, and the minimum output length
// for full security for KDF.Derive() for a one-stage KDF.
func (k KDF) ExtractSize() int {
switch k {
case KDF_HKDF_SHA256:
Expand All @@ -115,13 +165,19 @@ func (k KDF) ExtractSize() int {
return crypto.SHA384.Size()
case KDF_HKDF_SHA512:
return crypto.SHA512.Size()
case KDF_SHAKE128, KDF_TurboSHAKE128:
return 32
case KDF_SHAKE256, KDF_TurboSHAKE256:
return 64
default:
panic(ErrInvalidKDF)
}
}

// Extract derives a pseudorandom key from a high-entropy, secret input and a
// salt. The size of the output is determined by KDF.ExtractSize.
//
// Panics when called on a one-stage KDF.
func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) {
return hkdf.Extract(k.hash(), secret, salt)
}
Expand All @@ -130,6 +186,8 @@ func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) {
// and an information string. Panics if the pseudorandom key is less
// than N bytes, or if the output length is greater than 255*N bytes,
// where N is the size returned by KDF.Extract function.
//
// Panics when called on a one-stage KDF.
func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte {
extractSize := k.ExtractSize()
if len(pseudorandomKey) < extractSize {
Expand All @@ -148,6 +206,27 @@ func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte {
return output
}

// Derive derives a variable-length pseudorandom string from a
// high-entropy, secret input.
//
// Panics when called on a two-stage KDF.
func (k KDF) Derive(ikm []byte, l uint) []byte {
ret := make([]byte, l)
switch k {
case KDF_SHAKE128:
sha3.ShakeSum128(ret, ikm)
case KDF_SHAKE256:
sha3.ShakeSum256(ret, ikm)
case KDF_TurboSHAKE128:
sha3.TurboShakeSum128(ret, ikm, 0x1f)
case KDF_TurboSHAKE256:
sha3.TurboShakeSum256(ret, ikm, 0x1f)
default:
panic(ErrInvalidKDF)
}
return ret
}

func (k KDF) hash() func() hash.Hash {
switch k {
case KDF_HKDF_SHA256:
Expand Down Expand Up @@ -243,6 +322,9 @@ var (
dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM
hybridkemX25519Kyber768 hybridKEM
kemXwing genericNoAuthKEM
kemMLKEM512 genericNoAuthKEM
kemMLKEM768 genericNoAuthKEM
kemMLKEM1024 genericNoAuthKEM
)

func init() {
Expand Down Expand Up @@ -284,4 +366,11 @@ func init() {

kemXwing.Scheme = xwing.Scheme()
kemXwing.name = "HPKE_KEM_XWING"

kemMLKEM512.Scheme = mlkem512.Scheme()
kemMLKEM512.name = "HPKE_KEM_MLKEM512"
kemMLKEM768.Scheme = mlkem768.Scheme()
kemMLKEM768.name = "HPKE_KEM_MLKEM768"
kemMLKEM1024.Scheme = mlkem1024.Scheme()
kemMLKEM1024.name = "HPKE_KEM_MLKEM1024"
}
3 changes: 2 additions & 1 deletion hpke/hpke.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"crypto/rand"
"encoding"
"errors"
"fmt"
"io"

"github.com/cloudflare/circl/kem"
Expand Down Expand Up @@ -216,7 +217,7 @@ func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) {
seed := make([]byte, scheme.EncapsulationSeedSize())
_, err := io.ReadFull(rnd, seed)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("encapsulation seed: %w", err)
}

var enc, ss []byte
Expand Down
10 changes: 5 additions & 5 deletions hpke/hybridkem.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (h hybridKEM) Decapsulate(skr kem.PrivateKey, ct []byte) ([]byte, error) {
return nil, err
}

ss := append(ssA, ssB...)
ss := concat(ssA, ssB)

return ss, nil
}
Expand All @@ -81,8 +81,8 @@ func (h hybridKEM) EncapsulateDeterministically(
return nil, nil, err
}

ct = append(encA, encB...)
ss = append(ssA, ssB...)
ct = concat(encA, encB)
ss = concat(ssA, ssB)

return ct, ss, nil
}
Expand All @@ -106,7 +106,7 @@ func (k *hybridKEMPrivKey) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
return append(skA, skB...), nil
return concat(skA, skB), nil
}

func (k *hybridKEMPrivKey) Equal(sk kem.PrivateKey) bool {
Expand Down Expand Up @@ -143,7 +143,7 @@ func (k hybridKEMPubKey) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
return append(pkA, pkB...), nil
return concat(pkA, pkB), nil
}

func (k *hybridKEMPubKey) Equal(pk kem.PublicKey) bool {
Expand Down
32 changes: 17 additions & 15 deletions hpke/kembase.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ func (k kemBase) extractExpand(dh, kemCtx []byte) []byte {

func (k kemBase) labeledExtract(salt, label, info []byte) []byte {
suiteID := k.getSuiteID()
labeledIKM := append(append(append(append(
make([]byte, 0, len(versionLabel)+len(suiteID)+len(label)+len(info)),
versionLabel...),
suiteID[:]...),
label...),
info...)
labeledIKM := concat(
[]byte(versionLabel),
suiteID[:],
label,
info,
)
return hkdf.Extract(k.New, labeledIKM, salt)
}

Expand All @@ -68,11 +68,13 @@ func (k kemBase) labeledExpand(prk, label, info []byte, l uint16) []byte {
2+len(versionLabel)+len(suiteID)+len(label)+len(info),
)
binary.BigEndian.PutUint16(labeledInfo[0:2], l)
labeledInfo = append(append(append(append(labeledInfo,
versionLabel...),
suiteID[:]...),
label...),
info...)
labeledInfo = concat(
labeledInfo,
[]byte(versionLabel),
suiteID[:],
label,
info,
)
b := make([]byte, l)
rd := hkdf.Expand(k.New, prk, labeledInfo)
if _, err := io.ReadFull(rd, b); err != nil {
Expand Down Expand Up @@ -152,7 +154,7 @@ func (k dhKemBase) authEncap(
if err != nil {
return nil, nil, err
}
kemCtx = append(kemCtx, pkSm...)
kemCtx = concat(kemCtx, pkSm)

ss = k.extractExpand(dh, kemCtx)
return enc, ss, nil
Expand All @@ -177,7 +179,7 @@ func (k dhKemBase) coreEncap(
if err != nil {
return nil, nil, err
}
kemCtx = append(append([]byte{}, enc...), pkRm...)
kemCtx = concat(enc, pkRm)

return enc, kemCtx, nil
}
Expand Down Expand Up @@ -212,7 +214,7 @@ func (k dhKemBase) AuthDecapsulate(
if err != nil {
return nil, err
}
kemCtx = append(kemCtx, pkSm...)
kemCtx = concat(kemCtx, pkSm)
return k.extractExpand(dh, kemCtx), nil
}

Expand All @@ -237,5 +239,5 @@ func (k dhKemBase) coreDecap(
return nil, err
}

return append(append([]byte{}, ct...), pkRm...), nil
return concat(ct, pkRm), nil
}
4 changes: 2 additions & 2 deletions hpke/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *sealContext) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
return append([]byte{0}, rawContext...), nil
return concat([]byte{0}, rawContext), nil
}

// UnmarshalSealer parses an HPKE sealer.
Expand Down Expand Up @@ -134,7 +134,7 @@ func (c *openContext) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
return append([]byte{1}, rawContext...), nil
return concat([]byte{1}, rawContext), nil
}

// UnmarshalOpener parses a serialized HPKE opener and returns the corresponding
Expand Down
Loading