// Copyright 2024 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 implements Hybrid Public Key Encryption (HPKE) as defined in// [RFC 9180].//// [RFC 9180]: https://www.rfc-editor.org/rfc/rfc9180.html
package hpkeimport ()type context struct { suiteID []byte export func(string, uint16) ([]byte, error) 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.typeSenderstruct { *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.typeRecipientstruct { *context}func newContext( []byte, uint16, KDF, AEAD, []byte) (*context, error) { := suiteID(, .ID(), .ID())if .oneStage() { := make([]byte, 0, 2+2+len()) = byteorder.BEAppendUint16(, 0) // empty psk = byteorder.BEAppendUint16(, uint16(len())) = append(, ...) := make([]byte, 0, 1+2+2+len()) = append(, 0) // mode 0 = byteorder.BEAppendUint16(, 0) // empty psk_id = byteorder.BEAppendUint16(, uint16(len())) = append(, ...) , := .labeledDerive(, , "secret", ,uint16(.keySize()+.nonceSize()+.size()))if != nil {returnnil, } := [:.keySize()] := [.keySize() : .keySize()+.nonceSize()] := [.keySize()+.nonceSize():] , := .aead()if != nil {returnnil, } := func( string, uint16) ([]byte, error) {return .labeledDerive(, , "sec", []byte(), ) }return &context{aead: ,suiteID: ,export: ,baseNonce: , }, nil } , := .labeledExtract(, nil, "psk_id_hash", nil)if != nil {returnnil, } , := .labeledExtract(, nil, "info_hash", )if != nil {returnnil, } := append([]byte{0}, ...) = append(, ...) , := .labeledExtract(, , "secret", nil)if != nil {returnnil, } , := .labeledExpand(, , "key", , uint16(.keySize()))if != nil {returnnil, } , := .aead()if != nil {returnnil, } , := .labeledExpand(, , "base_nonce", , uint16(.nonceSize()))if != nil {returnnil, } , := .labeledExpand(, , "exp", , uint16(.size()))if != nil {returnnil, } := func( string, uint16) ([]byte, error) {return .labeledExpand(, , "sec", []byte(), ) }return &context{aead: ,suiteID: ,export: ,baseNonce: , }, nil}// 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 ( PublicKey, KDF, AEAD, []byte) ( []byte, *Sender, error) { , , := .encap()if != nil {returnnil, nil, } , := newContext(, .KEM().ID(), , , )if != nil {returnnil, nil, }return , &Sender{}, nil}// 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 ( []byte, PrivateKey, KDF, AEAD, []byte) (*Recipient, error) { , := .decap()if != nil {returnnil, } , := newContext(, .KEM().ID(), , , )if != nil {returnnil, }return &Recipient{}, nil}// 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 ( *Sender) (, []byte) ([]byte, error) {if .aead == nil {returnnil, errors.New("export-only instantiation") } := .aead.Seal(nil, .nextNonce(), , ) .seqNum++return , nil}// Seal instantiates a single-use HPKE sending HPKE context like [NewSender],// and then encrypts the provided plaintext like [Sender.Seal] (with no aad).// Seal returns the concatenation of the encapsulated key and the ciphertext.func ( PublicKey, KDF, AEAD, , []byte) ([]byte, error) { , , := NewSender(, , , )if != nil {returnnil, } , := .Seal(nil, )if != nil {returnnil, }returnappend(, ...), nil}// Export produces a secret value derived from the shared key between sender and// recipient. length must be at most 65,535.func ( *Sender) ( string, int) ([]byte, error) {if < 0 || > 0xFFFF {returnnil, errors.New("invalid length") }return .export(, uint16())}// 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 ( *Recipient) (, []byte) ([]byte, error) {if .aead == nil {returnnil, errors.New("export-only instantiation") } , := .aead.Open(nil, .nextNonce(), , )if != nil {returnnil, } .seqNum++return , nil}// Open instantiates a single-use HPKE receiving HPKE context like [NewRecipient],// and then decrypts the provided ciphertext like [Recipient.Open] (with no aad).// ciphertext must be the concatenation of the encapsulated key and the actual ciphertext.func ( PrivateKey, KDF, AEAD, , []byte) ([]byte, error) { := .KEM().encSize()iflen() < {returnnil, errors.New("ciphertext too short") } , := [:], [:] , := NewRecipient(, , , , )if != nil {returnnil, }return .Open(nil, )}// Export produces a secret value derived from the shared key between sender and// recipient. length must be at most 65,535.func ( *Recipient) ( string, int) ([]byte, error) {if < 0 || > 0xFFFF {returnnil, errors.New("invalid length") }return .export(, uint16())}func ( *context) () []byte { := make([]byte, .aead.NonceSize())byteorder.BEPutUint64([len()-8:], .seqNum)for := range .baseNonce { [] ^= .baseNonce[] }return}func suiteID(, , uint16) []byte { := make([]byte, 0, 4+2+2+2) = append(, []byte("HPKE")...) = byteorder.BEAppendUint16(, ) = byteorder.BEAppendUint16(, ) = byteorder.BEAppendUint16(, )return}
The pages are generated with Goldsv0.8.3-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.