Billionmail/BillionMail · error

invalid data length

Error message

invalid data length

What it means

Decrypt in the maillog_stat package base64-url-decodes an encrypted blob and requires at least 32 bytes (16-byte key + 16-byte IV material). If the decoded data is shorter than 32 bytes it returns 'invalid data length' without attempting AES decryption, indicating the ciphertext is truncated or was never produced by the matching Encrypt function.

Source

Thrown at core/internal/service/maillog_stat/encryption.go:55

	result := base64.URLEncoding.EncodeToString(resultBytes)
	return strings.TrimRight(result, "=")
}

func Decrypt(data string, result interface{}) (err error) {
	dataLength := len(data)
	amountToPad := 4 - (dataLength % 4)
	if amountToPad > 0 && amountToPad < 4 {
		data += strings.Repeat("=", amountToPad)
	}

	dataAes, err := base64.URLEncoding.DecodeString(data)
	if err != nil {
		return
	}

	if len(dataAes) < 32 {
		err = fmt.Errorf("invalid data length")
		return
	}

	dataAesLen := len(dataAes)
	keyiv := make([]byte, 0, 32)
	keyiv = append(keyiv, dataAes[:16]...)
	keyiv = append(keyiv, dataAes[dataAesLen-16:]...)

	var key, iv []byte
	for i := 0; i < 32; i++ {
		if i%2 == 0 {
			key = append(key, keyiv[i])
		} else {
			iv = append(iv, keyiv[i])
		}
	}

	block, err := aes.NewCipher(key)

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Confirm the value was produced by the matching Encrypt function in this package (key+IV+AES format)
  2. Validate the input is non-empty, valid base64 URL encoding, and decodes to >=32 bytes before calling Decrypt
  3. Check the DB column/data source for truncation (column length limits, manual edits)
  4. Re-encrypt the affected data since the original key material cannot be recovered from a truncated blob

Example fix

// before
plain, err := Decrypt(storedValue)
// after
raw, err := base64.URLEncoding.DecodeString(storedValue)
if err != nil || len(raw) < 32 {
    // handle: value is missing/corrupt; re-encrypt and store anew
    return fallbackValue
}
plain, err := Decrypt(storedValue)
Defensive patterns

Strategy: type-guard

Validate before calling

func isDecryptable(data string) bool {
    raw, err := base64.URLEncoding.DecodeString(data)
    return err == nil && len(raw) >= 32
}
if !isDecryptable(stored) { /* re-encrypt or use fallback */ }

Type guard

func validCiphertext(data string) bool {
    raw, err := base64.URLEncoding.DecodeString(data)
    return err == nil && len(raw) >= 32
}

Try / catch

plain, err := Decrypt(stored)
if err != nil {
    if err.Error() == "invalid data length" || errors.Is(err, base64.CorruptInputError(0)) {
        // treat as lost data: log, re-encrypt source, return zero value
        return zeroValue, nil
    }
    return nil, err
}

Prevention

When it happens

Trigger: Decrypt(data) is called with a string that decodes to fewer than 32 bytes — e.g. an empty string, invalid base64 that decoded to partial garbage, a manually truncated value, or a value encrypted/shortened by a different scheme.

Common situations: Stored database values corrupted or written by an older/other code path; passing plaintext instead of encrypted data; passing the base64 of a short value from tests; manual copy-paste truncation of tokens.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/f37b4cd01d320e3f. Report an issue: GitHub.