package hpke import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/elliptic" _ "crypto/sha256" // Linking sha256. _ "crypto/sha512" // Linking sha512. "fmt" "hash" "io" "github.com/cloudflare/circl/dh/x25519" "github.com/cloudflare/circl/dh/x448" "github.com/cloudflare/circl/ecc/p384" "github.com/cloudflare/circl/kem" "github.com/cloudflare/circl/kem/kyber/kyber768" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" ) type KEM uint16 //nolint:golint,stylecheck const ( // KEM_P256_HKDF_SHA256 is a KEM using P256 curve and HKDF with SHA-256. KEM_P256_HKDF_SHA256 KEM = 0x10 // KEM_P384_HKDF_SHA384 is a KEM using P384 curve and HKDF with SHA-384. KEM_P384_HKDF_SHA384 KEM = 0x11 // KEM_P521_HKDF_SHA512 is a KEM using P521 curve and HKDF with SHA-512. KEM_P521_HKDF_SHA512 KEM = 0x12 // KEM_X25519_HKDF_SHA256 is a KEM using X25519 Diffie-Hellman function // and HKDF with SHA-256. KEM_X25519_HKDF_SHA256 KEM = 0x20 // KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and // HKDF with SHA-512. KEM_X448_HKDF_SHA512 KEM = 0x21 // KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256) // and Kyber768Draft00 KEM_X25519_KYBER768_DRAFT00 KEM = 0x30 ) // IsValid returns true if the KEM identifier is supported by the HPKE package. func (k KEM) IsValid() bool { switch k { case KEM_P256_HKDF_SHA256, KEM_P384_HKDF_SHA384, KEM_P521_HKDF_SHA512, KEM_X25519_HKDF_SHA256, KEM_X448_HKDF_SHA512, KEM_X25519_KYBER768_DRAFT00: return true default: return false } } // Scheme returns an instance of a KEM that supports authentication. Panics if // the KEM identifier is invalid. func (k KEM) Scheme() kem.AuthScheme { switch k { case KEM_P256_HKDF_SHA256: return dhkemp256hkdfsha256 case KEM_P384_HKDF_SHA384: return dhkemp384hkdfsha384 case KEM_P521_HKDF_SHA512: return dhkemp521hkdfsha512 case KEM_X25519_HKDF_SHA256: return dhkemx25519hkdfsha256 case KEM_X448_HKDF_SHA512: return dhkemx448hkdfsha512 case KEM_X25519_KYBER768_DRAFT00: return hybridkemX25519Kyber768 default: panic(ErrInvalidKEM) } } type KDF uint16 //nolint:golint,stylecheck const ( // KDF_HKDF_SHA256 is a 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 KDF = 0x02 // KDF_HKDF_SHA512 is a KDF using HKDF with SHA-512. KDF_HKDF_SHA512 KDF = 0x03 ) func (k KDF) IsValid() bool { switch k { case KDF_HKDF_SHA256, KDF_HKDF_SHA384, KDF_HKDF_SHA512: return true default: return false } } // ExtractSize returns the size (in bytes) of the pseudorandom key produced // by KDF.Extract. func (k KDF) ExtractSize() int { switch k { case KDF_HKDF_SHA256: return crypto.SHA256.Size() case KDF_HKDF_SHA384: return crypto.SHA384.Size() case KDF_HKDF_SHA512: return crypto.SHA512.Size() 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. func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) { return hkdf.Extract(k.hash(), secret, salt) } // Expand derives a variable length pseudorandom string from a pseudorandom key // 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. func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte { extractSize := k.ExtractSize() if len(pseudorandomKey) < extractSize { panic(fmt.Errorf("pseudorandom key must be %v bytes", extractSize)) } maxLength := uint(255 * extractSize) if outputLen > maxLength { panic(fmt.Errorf("output length must be less than %v bytes", maxLength)) } output := make([]byte, outputLen) rd := hkdf.Expand(k.hash(), pseudorandomKey[:extractSize], info) _, err := io.ReadFull(rd, output) if err != nil { panic(err) } return output } func (k KDF) hash() func() hash.Hash { switch k { case KDF_HKDF_SHA256: return crypto.SHA256.New case KDF_HKDF_SHA384: return crypto.SHA384.New case KDF_HKDF_SHA512: return crypto.SHA512.New default: panic(ErrInvalidKDF) } } type AEAD uint16 //nolint:golint,stylecheck const ( // AEAD_AES128GCM is AES-128 block cipher in Galois Counter Mode (GCM). AEAD_AES128GCM AEAD = 0x01 // AEAD_AES256GCM is AES-256 block cipher in Galois Counter Mode (GCM). AEAD_AES256GCM AEAD = 0x02 // AEAD_ChaCha20Poly1305 is ChaCha20 stream cipher and Poly1305 MAC. AEAD_ChaCha20Poly1305 AEAD = 0x03 ) // New instantiates an AEAD cipher from the identifier, returns an error if the // identifier is not known. func (a AEAD) New(key []byte) (cipher.AEAD, error) { switch a { case AEAD_AES128GCM, AEAD_AES256GCM: block, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewGCM(block) case AEAD_ChaCha20Poly1305: return chacha20poly1305.New(key) default: panic(ErrInvalidAEAD) } } func (a AEAD) IsValid() bool { switch a { case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305: return true default: return false } } // KeySize returns the size in bytes of the keys used by the AEAD cipher. func (a AEAD) KeySize() uint { switch a { case AEAD_AES128GCM: return 16 case AEAD_AES256GCM: return 32 case AEAD_ChaCha20Poly1305: return chacha20poly1305.KeySize default: panic(ErrInvalidAEAD) } } // NonceSize returns the size in bytes of the nonce used by the AEAD cipher. func (a AEAD) NonceSize() uint { switch a { case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305: return 12 default: panic(ErrInvalidAEAD) } } // CipherLen returns the length of a ciphertext corresponding to a message of // length mLen. func (a AEAD) CipherLen(mLen uint) uint { switch a { case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305: return mLen + 16 default: panic(ErrInvalidAEAD) } } var ( dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM hybridkemX25519Kyber768 hybridKEM ) func init() { dhkemp256hkdfsha256.Curve = elliptic.P256() dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256 dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256" dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256 dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256 dhkemp384hkdfsha384.Curve = p384.P384() dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384 dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384" dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384 dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384 dhkemp521hkdfsha512.Curve = elliptic.P521() dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512 dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512" dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512 dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512 dhkemx25519hkdfsha256.size = x25519.Size dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256 dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256" dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256 dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256 dhkemx448hkdfsha512.size = x448.Size dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512 dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512" dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512 dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512 hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00 hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256" hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256 hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256 hybridkemX25519Kyber768.kemB = kyber768.Scheme() }