FiloSottile/age · error

invalid identity encoding: %v

Error message

invalid identity encoding: %v

What it means

ParseIdentity decodes a plugin identity string, which must be a bech32 string whose HRP starts with AGE-PLUGIN- and ends with '-'. This error means the input isn't valid bech32 at all, and it wraps the underlying bech32 decoder error for context.

Source

Thrown at plugin/encode.go:32

	"filippo.io/hpke"
)

// EncodeIdentity encodes a plugin identity string for a plugin with the given
// name. If the name is invalid, it returns an empty string.
func EncodeIdentity(name string, data []byte) string {
	if !validPluginName(name) {
		return ""
	}
	s, _ := bech32.Encode("AGE-PLUGIN-"+strings.ToUpper(name)+"-", data)
	return s
}

// ParseIdentity decodes a plugin identity string. It returns the plugin name
// in lowercase and the encoded data.
func ParseIdentity(s string) (name string, data []byte, err error) {
	hrp, data, err := bech32.Decode(s)
	if err != nil {
		return "", nil, fmt.Errorf("invalid identity encoding: %v", err)
	}
	if !strings.HasPrefix(hrp, "AGE-PLUGIN-") || !strings.HasSuffix(hrp, "-") {
		return "", nil, fmt.Errorf("not a plugin identity: %v", err)
	}
	name = strings.TrimSuffix(strings.TrimPrefix(hrp, "AGE-PLUGIN-"), "-")
	name = strings.ToLower(name)
	if !validPluginName(name) {
		return "", nil, fmt.Errorf("invalid plugin name: %q", name)
	}
	return name, data, nil
}

// EncodeRecipient encodes a plugin recipient string for a plugin with the given
// name. If the name is invalid, it returns an empty string.
func EncodeRecipient(name string, data []byte) string {
	if !validPluginName(name) {
		return ""
	}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Check the identity file contains a full AGE-PLUGIN-XXXX-1... bech32 string with no truncation or stray whitespace (strings.TrimSpace before parsing)
  2. Confirm you're using an identity string, not an age1... recipient — they are not interchangeable
  3. Regenerate or re-export the identity from the plugin tool; the string is likely corrupted
  4. Read the wrapped %v bech32 error — it pinpoints checksum vs. charset vs. length problems

Example fix

// before
name, data, err := plugin.ParseIdentity(strings.TrimSpace(maybeWithNewline + ""))
// after
s := strings.TrimSpace(rawIdentity)
if s == "" || !strings.HasPrefix(strings.ToUpper(s), "AGE-PLUGIN-") {
    return fmt.Errorf("not a plugin identity file")
}
name, data, err := plugin.ParseIdentity(s)
Defensive patterns

Strategy: validation

Validate before calling

func looksLikePluginIdentity(s string) bool {
    s = strings.TrimSpace(s)
    up := strings.ToUpper(s)
    return strings.HasPrefix(up, "AGE-PLUGIN-") && strings.HasSuffix(up, "-1") && len(s) > len("AGE-PLUGIN--1")
}
// call before ParseIdentity

Type guard

func isPluginIdentity(s string) bool {
    _, _, err := plugin.ParseIdentity(strings.TrimSpace(s))
    return err == nil
}

Try / catch

name, data, err := plugin.ParseIdentity(s)
if err != nil {
    return fmt.Errorf("identity file is not a valid plugin identity (bech32 decode failed): %w", err)
}

Prevention

When it happens

Trigger: Passing a string that is not bech32 (truncated, wrong checksum, invalid characters, lowercase/uppercase mixing beyond HRP rules, or a completely different format like a raw file path) to ParseIdentity, directly or via NewIdentity/IdentityV1 when parsing AGE-PLUGIN-... identity files.

Common situations: Identity file contains a recipient string instead of an identity, or vice versa; file was truncated by copy/paste or a bad transfer; user pasted a key from a different tool (SSH key, age1... recipient); trailing newline/whitespace not trimmed before parsing.

Related errors


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