FiloSottile/age · error

failed to set up HPKE sender: %v

Error message

failed to set up HPKE sender: %v

What it means

WrapWithLabels creates an HPKE sender against the recipient's public key using HKDF-SHA256, ChaCha20Poly1305, and a KEM-specific info label. NewSender validates the KEM/AEAD/id combination; failure here means the internal recipient key or the HPKE parameter set was rejected.

Source

Thrown at tag/tag.go:144

	return tag[:4], nil
}

// WrapWithLabels implements [age.RecipientWithLabels], returning a single
// "postquantum" label if r is a hybrid P-256 + ML-KEM-768 recipient. This
// ensures a hybrid Recipient can't be mixed with other recipients that would
// defeat its post-quantum security.
//
// To unsafely bypass this restriction, wrap Recipient in an [age.Recipient]
// type that doesn't expose WrapWithLabels.
func (r *Recipient) WrapWithLabels(fileKey []byte) ([]*age.Stanza, []string, error) {
	label, arg := "age-encryption.org/p256tag", "p256tag"
	if r.Hybrid() {
		label, arg = "age-encryption.org/mlkem768p256tag", "mlkem768p256tag"
	}

	enc, s, err := hpke.NewSender(r.pk, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte(label))
	if err != nil {
		return nil, nil, fmt.Errorf("failed to set up HPKE sender: %v", err)
	}
	ct, err := s.Seal(nil, fileKey)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to encrypt file key: %v", err)
	}

	tag, err := r.Tag(enc)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to compute tag: %v", err)
	}

	l := &age.Stanza{
		Type: arg,
		Args: []string{
			format.EncodeToString(tag[:4]),
			format.EncodeToString(enc),
		},
		Body: ct,

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Always obtain the Recipient from tag.ParseRecipient, NewClassicRecipient, or NewHybridRecipient — never instantiate tag.Recipient directly.
  2. Check the wrapped %v detail for the underlying HPKE error and confirm your filippo.io/hpke version is current (go get -u filippo.io/hpke).
  3. Verify the key was not truncated or mutated between construction and use.
  4. If serializing recipients, serialize the raw bytes and re-parse with ParseRecipient rather than persisting structs.

Example fix

// before
r := &tag.Recipient{} // zero value, nil pk
stanzas, err := r.Wrap(fileKey)
// after
r, err := tag.NewHybridRecipient(rawPubKey)
if err != nil { return err }
stanzas, err := r.Wrap(fileKey)
Defensive patterns

Strategy: validation

Validate before calling

r, err := tag.NewHybridRecipient(pubKey) // or NewClassicRecipient / ParseRecipient
if err != nil {
    return err // fail fast before any Wrap call
}

Type guard

func constructedViaAPI(r *tag.Recipient) bool { return r != nil } // zero-value Recipient{} has nil pk and will fail Wrap

Try / catch

stanzas, _, err := r.WrapWithLabels(fileKey)
if err != nil {
    return fmt.Errorf("wrap failed: %w", err)
}

Prevention

When it happens

Trigger: Calling Wrap/WrapWithLabels on a Recipient whose internal hpke.PublicKey is invalid or whose KEM conflicts with the chosen suites — practically only reachable if the Recipient was constructed with a zero value (&tag.Recipient{} with nil pk) or corrupted via unsafe code, since exported constructors validate keys.

Common situations: Constructing Recipient via reflection/unsafe or a zero-value struct instead of NewClassicRecipient/NewHybridRecipient; deserializing a Recipient from an external format; a mismatched hpke library version where KEM IDs changed.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/7084c5195867d836. Report an issue: GitHub.