FiloSottile/age · error

failed to encrypt file key: %v

Error message

failed to encrypt file key: %v

What it means

After the HPKE sender is set up, WrapWithLabels seals the 16-byte age file key with ChaCha20Poly1305 via s.Seal. HPKE Seal fails if the plaintext exceeds the AEAD's size limits or the context is in an invalid state; for a 16-byte file key this is effectively unreachable, but it is wrapped and reported here.

Source

Thrown at tag/tag.go:148

// "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,
	}

	if r.Hybrid() {
		return []*age.Stanza{l}, []string{"postquantum"}, nil

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Pass exactly the 16-byte age file key to Wrap/WrapWithLabels.
  2. Use the standard crypto stack (unmodified filippo.io/hpke) and a current Go toolchain.
  3. Inspect the wrapped %v detail to identify the underlying Seal failure.
  4. Retry encryption with a freshly generated file key if the input may be corrupted.

Example fix

// before
ct, err := r.Wrap(entireHeader) // oversized plaintext
// after
var fileKey [16]byte
// ... fill fileKey ...
stanzas, err := r.Wrap(fileKey[:])
Defensive patterns

Strategy: validation

Validate before calling

const fileKeySize = 16
func validFileKey(k []byte) bool { return len(k) == fileKeySize }

Type guard

func is16Bytes(b []byte) bool { return len(b) == 16 }

Try / catch

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

Prevention

When it happens

Trigger: Calling Wrap/WrapWithLabels with a fileKey exceeding ChaCha20Poly1305 plaintext limits, or with an HPKE sender context that failed to initialize cleanly (only reachable through abnormal internal state or a swapped crypto backend).

Common situations: Plugin code passing an oversized buffer instead of the 16-byte file key; patched crypto backends; memory-corruption style bugs in exotic environments.

Related errors


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