FiloSottile/age · error
illegal zero padding
Error message
illegal zero padding
What it means
convertBits regroups data between bit widths and requires leftover bits to form valid padding. When pad=false (used on decode, 5→8 conversion), if more than tobits-frombits leftover bits remain (bits >= frombits), the padding itself would carry data bits, which is illegal in bech32. The error signals the decoded data part does not represent a whole number of bytes.
Source
Thrown at internal/bech32/bech32.go:100
bits := byte(0)
maxv := byte(1<<tobits - 1)
for idx, value := range data {
if value>>frombits != 0 {
return nil, fmt.Errorf("invalid data range: data[%d]=%d (frombits=%d)", idx, value, frombits)
}
acc = acc<<frombits | uint32(value)
bits += frombits
for bits >= tobits {
bits -= tobits
ret = append(ret, byte(acc>>bits)&maxv)
}
}
if pad {
if bits > 0 {
ret = append(ret, byte(acc<<(tobits-bits))&maxv)
}
} else if bits >= frombits {
return nil, fmt.Errorf("illegal zero padding")
} 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 {View on GitHub (pinned to b74dce4cdb)
Solutions
- Treat the input string as invalid bech32 data; do not attempt to repair it — re-obtain the original string from its source
- Verify the string was generated by a conformant bech32 encoder (age's Encode pads correctly); re-encode from the raw key material if you have it
- Check the string was not truncated or extended during copy/paste — compare length with the expected format for age identities/recipients
Defensive patterns
Strategy: validation
Validate before calling
func isCanonicalBech32Payload(nGroups int) bool {
// 5-bit groups must convert to whole 8-bit bytes with < 5 bits of padding
return (nGroups*5)%8 < 5
} Prevention
- Treat any decode padding error as evidence of tampering or corruption; never repair by hand
- Only exchange bech32 strings produced by conformant encoders (age itself)
- Protect age key strings from manual editing; store and transfer them verbatim
- Validate string length against expected format before parsing
When it happens
Trigger: Calling bech32.Decode on a string whose data payload length in 5-bit groups is not compatible with whole 8-bit bytes — i.e. len(data)*5 mod 8 leaves >= 5 bits of padding — typically a hand-modified or maliciously crafted bech32 string, or one encoded by a non-conforming encoder.
Common situations: Manually edited bech32 strings (e.g. truncated or extended data part); strings produced by other tools that pad incorrectly; corrupted age identity/recipient strings that still pass the bech32 checksum but have invalid padding.
Related errors
- non-zero padding
- invalid data range: data[%d]=%d (frombits=%d)
- invalid HRP: %q
- invalid HRP character: hrp[%d]=%d
- mixed case HRP: %q
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/d75a5ae077e13283.
Report an issue: GitHub.