FiloSottile/age · error
invalid HRP character: hrp
Error message
invalid HRP character: hrp[%d]=%d
What it means
Bech32 restricts HRP characters to the printable US-ASCII range 33–126. bech32.Encode scans every rune of the HRP and rejects any character outside this range. This guarantees the encoded string contains only valid bech32 characters.
Solutions
- Sanitize the HRP before encoding: strip whitespace/control characters with strings.TrimSpace and strings.Map, or reject non-ASCII input at the source
- Verify the HRP source encoding — read files as UTF-8/ASCII and trim line endings (strings.TrimRight(s, "\r\n"))
- For age usage, use the fixed constants ("age", "AGE-SECRET-KEY-") instead of deriving the HRP from variable input
Example fix
// before
hrp := string(prefixBytes) // may contain "\n" or non-ASCII
s, err := bech32.Encode(hrp, data) // "invalid HRP character"
// after
hrp := strings.TrimSpace(string(prefixBytes))
for _, c := range hrp {
if c < 33 || c > 126 {
return fmt.Errorf("HRP contains invalid character %q", c)
}
}
s, err := bech32.Encode(hrp, data) Defensive patterns
Strategy: validation
Validate before calling
func validHRPChars(hrp string) bool {
for _, c := range hrp {
if c < 33 || c > 126 { return false }
}
return true
} Prevention
- Trim whitespace and line endings from any HRP read from files or argv
- Restrict HRP inputs to ASCII at the API boundary; reject Unicode early
- Read config files with explicit UTF-8 handling to avoid mis-decoded high bytes
- Use fixed age constants instead of user-supplied HRPs wherever possible
When it happens
Trigger: Calling bech32.Encode with an HRP containing control characters, spaces (< 33), high bytes or non-ASCII Unicode (> 126) — e.g. an HRP built from user input, a mis-decoded byte slice, or a prefix with a trailing newline.
Common situations: HRP values read from files or argv that include a trailing "\n"; strings decoded from UTF-16 or Latin-1 sources producing bytes > 126; user-supplied prefixes containing spaces or emoji; log-formatted strings accidentally used as HRPs.
Related errors
- invalid HRP
- mixed case HRP
- illegal zero padding
- invalid character human-readable part: s
- invalid data range: data
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/c40a447fbf855e60.
Report an issue: GitHub.
Appendix: source
Thrown at internal/bech32/bech32.go:119
} else if byte(acc<<(tobits-bits))&maxv != 0 {
return nil, fmt.Errorf("non-zero padding")
}
return ret, nil
}
// Encode encodes the HRP and a bytes slice to Bech32. If the HRP is uppercase,
// the output will be uppercase.
func Encode(hrp string, data []byte) (string, error) {
values, err := convertBits(data, 8, 5, true)
if err != nil {
return "", err
}
if len(hrp) < 1 {
return "", fmt.Errorf("invalid HRP: %q", hrp)
}
for p, c := range hrp {
if c < 33 || c > 126 {
return "", fmt.Errorf("invalid HRP character: hrp[%d]=%d", p, c)
}
}
if strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp {
return "", fmt.Errorf("mixed case HRP: %q", hrp)
}
lower := strings.ToLower(hrp) == hrp
hrp = strings.ToLower(hrp)
var ret strings.Builder
ret.WriteString(hrp)
ret.WriteString("1")
for _, p := range values {
ret.WriteByte(charset[p])
}
for _, p := range createChecksum(hrp, values) {
ret.WriteByte(charset[p])
}
if lower {
return ret.String(), nilView on GitHub (pinned to b74dce4cdb)