FiloSottile/age · error
failed to create hybrid public key: %v
Error message
failed to create hybrid public key: %v
What it means
After curve validation, EncodeHybridRecipient calls hpke.NewHybridPublicKey to combine the MLKEM768 encapsulation key and X25519 key. If that construction fails (nil/invalid key material, wrong key sizes, malformed encapsulation key), the error is wrapped as 'failed to create hybrid public key'.
Source
Thrown at plugin/encode.go:105
// identities that are compatible with native recipients.
func EncodeX25519Recipient(pk *ecdh.PublicKey) (string, error) {
if pk.Curve() != ecdh.X25519() {
return "", fmt.Errorf("wrong ecdh Curve")
}
return bech32.Encode("age", pk.Bytes())
}
// EncodeHybridRecipient encodes a native MLKEM768-X25519 recipient from a
// [crypto/mlkem.EncapsulationKey768] and a [crypto/ecdh.X25519] public key.
// It's meant for plugins that implement identities that are compatible with
// native recipients.
func EncodeHybridRecipient(pq *mlkem.EncapsulationKey768, t *ecdh.PublicKey) (string, error) {
if t.Curve() != ecdh.X25519() {
return "", fmt.Errorf("wrong ecdh Curve")
}
pk, err := hpke.NewHybridPublicKey(pq, t)
if err != nil {
return "", fmt.Errorf("failed to create hybrid public key: %v", err)
}
return bech32.Encode("age1pq", pk.Bytes())
}
View on GitHub (pinned to b74dce4cdb)
Solutions
- Verify pq is non-nil and is a valid *mlkem.EncapsulationKey768 (Bytes() returns 1184 bytes).
- Regenerate keys with mlkem.NewEncapsulationKey768 / GenerateKey768 rather than hand-constructing.
- Check t.Bytes() is exactly 32 bytes of X25519 public key.
- Log the underlying wrapped error from hpke.NewHybridPublicKey for the specific cause.
Example fix
// before
var pq *mlkem.EncapsulationKey768 // nil
s, err := plugin.EncodeHybridRecipient(pq, t.PublicKey())
// after
pq, err := mlkem.NewEncapsulationKey768(pqBytes)
if err != nil { return err }
s, err := plugin.EncodeHybridRecipient(pq, t.PublicKey()) Defensive patterns
Strategy: try-catch
Validate before calling
if pq == nil || len(pq.Bytes()) != 1184 { return errors.New("invalid MLKEM768 encapsulation key") }
if t == nil || len(t.Bytes()) != 32 { return errors.New("invalid X25519 public key") } Type guard
func validHybridInputs(pq *mlkem.EncapsulationKey768, t *ecdh.PublicKey) bool {
return pq != nil && len(pq.Bytes()) == 1184 && t != nil && t.Curve() == ecdh.X25519()
} Try / catch
s, err := plugin.EncodeHybridRecipient(pq, t)
if err != nil {
return fmt.Errorf("EncodeHybridRecipient: %w", err) // log wrapped hpke cause
} Prevention
- Regenerate keys via mlkem rather than hand-building from bytes
- Validate key byte lengths (1184 for MLKEM768 ek, 32 for X25519) after deserialization
- Check the mlkem768 variant, not 512/1024
When it happens
Trigger: Passing a nil or zero-value *mlkem.EncapsulationKey768, an encapsulation key not produced by mlkem key generation/decapsulation-key export, or key bytes that fail hpke.NewHybridPublicKey's internal validation.
Common situations: Deserializing PQ keys from untrusted/corrupted storage; passing an EncapsulationKey64 (MLKEM512) where 768 is required; constructing keys from truncated byte slices.
Related errors
- invalid tagpq recipient public key: %v
- invalid ciphertext size
- failed to set up HPKE sender: %v
- failed to decrypt and authenticate final chunk: %w
- failed to decrypt and authenticate chunk at offset %d: %w
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/c6a2067cc523f6ad.
Report an issue: GitHub.