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

  1. Treat the input string as invalid bech32 data; do not attempt to repair it — re-obtain the original string from its source
  2. 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
  3. 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

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


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