// 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/sha3" "crypto/sha512" "errors" "fmt" "hash" "internal/byteorder" ) // The KDF is one of the three components of an HPKE ciphersuite, implementing // key derivation. type KDF interface { ID() uint16 oneStage() bool size() int // Nh labeledDerive(suiteID, inputKey []byte, label string, context []byte, length uint16) ([]byte, error) labeledExtract(suiteID, 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 case 0x0010: // SHAKE128 return SHAKE128(), nil case 0x0011: // SHAKE256 return SHAKE256(), 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 nH int } var hkdfSHA256 = &hkdfKDF{hash: sha256.New, id: 0x0001, nH: sha256.Size} var hkdfSHA384 = &hkdfKDF{hash: sha512.New384, id: 0x0002, nH: sha512.Size384} var hkdfSHA512 = &hkdfKDF{hash: sha512.New, id: 0x0003, nH: sha512.Size} func (kdf *hkdfKDF) ID() uint16 { return kdf.id } func (kdf *hkdfKDF) size() int { return kdf.nH } func (kdf *hkdfKDF) oneStage() bool { return false } func (kdf *hkdfKDF) labeledDerive(_, _ []byte, _ string, _ []byte, _ uint16) ([]byte, error) { return nil, errors.New("hpke: internal error: labeledDerive called on two-stage KDF") } func (kdf *hkdfKDF) labeledExtract(suiteID []byte, salt []byte, label string, inputKey []byte) ([]byte, error) { labeledIKM := make([]byte, 0, 7+len(suiteID)+len(label)+len(inputKey)) labeledIKM = append(labeledIKM, []byte("HPKE-v1")...) labeledIKM = append(labeledIKM, suiteID...) 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 = 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, randomKey, string(labeledInfo), int(length)) } // SHAKE128 returns a SHAKE128 KDF implementation. func SHAKE128() KDF { return shake128KDF } // SHAKE256 returns a SHAKE256 KDF implementation. func SHAKE256() KDF { return shake256KDF } type shakeKDF struct { hash func() *sha3.SHAKE id uint16 nH int } var shake128KDF = &shakeKDF{hash: sha3.NewSHAKE128, id: 0x0010, nH: 32} var shake256KDF = &shakeKDF{hash: sha3.NewSHAKE256, id: 0x0011, nH: 64} func (kdf *shakeKDF) ID() uint16 { return kdf.id } func (kdf *shakeKDF) size() int { return kdf.nH } func (kdf *shakeKDF) oneStage() bool { return true } func (kdf *shakeKDF) labeledDerive(suiteID, inputKey []byte, label string, context []byte, length uint16) ([]byte, error) { H := kdf.hash() H.Write(inputKey) H.Write([]byte("HPKE-v1")) H.Write(suiteID) H.Write([]byte{byte(len(label) >> 8), byte(len(label))}) H.Write([]byte(label)) H.Write([]byte{byte(length >> 8), byte(length)}) H.Write(context) out := make([]byte, length) H.Read(out) return out, nil } func (kdf *shakeKDF) labeledExtract(_, _ []byte, _ string, _ []byte) ([]byte, error) { return nil, errors.New("hpke: internal error: labeledExtract called on one-stage KDF") } func (kdf *shakeKDF) labeledExpand(_, _ []byte, _ string, _ []byte, _ uint16) ([]byte, error) { return nil, errors.New("hpke: internal error: labeledExpand called on one-stage KDF") }