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"}, nilView on GitHub (pinned to b74dce4cdb)
Solutions
- Pass exactly the 16-byte age file key to Wrap/WrapWithLabels.
- Use the standard crypto stack (unmodified filippo.io/hpke) and a current Go toolchain.
- Inspect the wrapped %v detail to identify the underlying Seal failure.
- 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
- Pass exactly the 16-byte age file key.
- Use unmodified filippo.io/hpke and a current Go toolchain.
- Include the wrapped error text in diagnostics.
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
- invalid tagpq recipient public key: %v
- invalid ciphertext size
- failed to set up HPKE sender: %v
- ArmoredWriter already closed
- trailing data after armored file
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/6afddd1e15d5c98e.
Report an issue: GitHub.