FiloSottile/age · error

non-zero padding

Error message

non-zero padding

What it means

This is the companion to "illegal zero padding": in convertBits with pad=false (decode, 5→8 direction), leftover bits below frombits are acceptable padding only if they are all zero. If the leftover bits form a non-zero value, the payload encodes more information than a byte-aligned representation allows, and bech32.Decode rejects the string.

Source

Thrown at internal/bech32/bech32.go:102

	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 {
			return "", fmt.Errorf("invalid HRP character: hrp[%d]=%d", p, c)
		}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Reject the string as non-canonical bech32; it cannot be trusted even if the checksum passes — obtain the original string from a trusted source
  2. If you generated it with your own encoder, fix the encoder to zero-pad the final group (append byte(acc<<(tobits-bits))&maxv with zeroed bits)
  3. For age usage, re-derive the recipient/identity string with bech32.Encode from the raw key bytes rather than editing the string
Defensive patterns

Strategy: validation

Validate before calling

func isCanonicalBech32(s string) bool {
	if strings.ToLower(s) != s && strings.ToUpper(s) != s { return false }
	hrp, data, err := bech32.Decode(s)
	return err == nil && hrp != "" && len(data) > 0
}

Prevention

When it happens

Trigger: Calling bech32.Decode on a bech32 string whose final partial byte's padding bits are non-zero — the data part is byte-length-consistent but the discarded bits are not zero, indicating a deliberately or accidentally crafted non-canonical string.

Common situations: Fuzzed or adversarial bech32 inputs (this check exists to enforce canonical encoding); strings produced by non-conformant third-party encoders that leave garbage in padding bits; tampering with age recipient/identity strings where an attacker flips low-order bits that fall in padding.

Related errors


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