Fix Base64 Decode Errors: Padding & Invalid Char
A Base64 string that refuses to decode almost always fails for one of three reasons: padding was lost, an illegal character snuck in, or the wrong alphabet was used. The error messages are vague on purpose. Python raises binascii.Error: Incorrect padding or Invalid base64-encoded string, Java throws IllegalArgumentException, and JavaScript's atob() throws an InvalidCharacterError. None of them tell you which of the three problems you actually have. This guide walks through all three, in order of likelihood, with fixes you can paste straight into your code.
The fastest way to diagnose any of these is to drop the broken string into an in-browser decoder. Paste it into the Base64 encoder and decoder, try standard Decode, then try URL-Safe Decode. If one of them produces readable output, you have your answer in seconds without writing a line of code. Everything below explains why that works.
Cause 1: Lost padding from copy-paste or trimming
Base64 encodes three bytes of input into four output characters. When the input length is not a multiple of three, the encoder pads the final group with one or two = characters so the output length is always a multiple of four. Many tools, editors, and JSON serializers silently strip trailing = signs, and a careless copy-paste can drop them too. Strict decoders then complain about incorrect padding.
The fix is to re-add = until the string length is a multiple of four. The number you need follows directly from the length modulo four:
- Length mod 4 == 0: the string is already complete, no padding needed.
- Length mod 4 == 2: append
==(the last group encoded a single byte). - Length mod 4 == 3: append
=(the last group encoded two bytes).
In Python you can compute this without thinking about it:
import base64
def decode_padded(s: str) -> bytes:
s = s.strip()
s += "=" * (-len(s) % 4) # pads to next multiple of 4
return base64.b64decode(s)
The expression -len(s) % 4 yields 0, 1, 2, or 3, which is exactly the number of = characters required. This relates to the mod-3 rule on the encode side: when the input byte count leaves a remainder of 1 you get two padding characters, and a remainder of 2 gives you one. The two views describe the same boundary.
The exception padding cannot fix
If length mod 4 equals 1, stop. No valid Base64 string ever has a length of the form 4n+1, because a single leftover character cannot encode any whole bytes. Python is explicit about this: with strict validation it reports Invalid base64-encoded string: number of data characters cannot be 1 more than a multiple of 4. A remainder of 1 means real characters were truncated, not that padding is missing. Adding = here only produces a different, equally wrong result. Go back to the source and recover the missing characters; the string itself is incomplete.
Cause 2: Invalid characters from whitespace or homoglyphs
The second class is an illegal character inside the payload. The usual culprit is whitespace: a newline every 76 characters (the MIME convention from RFC 2045), tabs, or trailing spaces introduced when the string was wrapped across lines in an email, certificate, or log file. Strict decoders such as JavaScript's atob() reject any character outside the alphabet, including \n. (Python's base64.b64decode() is lenient by default and silently discards stray non-alphabet bytes unless you pass validate=True, which is why the same string can fail in the browser yet appear to work in a Python script.)
If you control the encoding step on Linux, prevent the wrapping at the source by disabling line breaks with GNU coreutils:
base64 -w0 input.bin > output.txt
If you only have the broken string, strip everything that is not a valid Base64 character before decoding. A regex filter that keeps only the standard and URL-safe alphabets plus padding is the safe approach:
import re, base64
cleaned = re.sub(r"[^A-Za-z0-9+/=_-]", "", raw_string)
data = base64.b64decode(cleaned + "=" * (-len(cleaned) % 4))
A subtler version of this problem is homoglyphs: characters that look identical to Base64 letters but are different Unicode code points, such as a Cyrillic letter pasted from a styled document or a webpage. They render the same but are not in the alphabet, so the decoder rejects them while your eyes see nothing wrong. When a string looks visually perfect but still fails, run it through the homoglyph detector to surface any disguised characters, and use the whitespace visualizer to reveal hidden tabs, non-breaking spaces, or zero-width characters that a plain text view hides.
Cause 3: Standard vs URL-safe alphabet mismatch
This is the most common real cause and the one most troubleshooting pages miss. RFC 4648 defines two alphabets. Standard Base64 uses + and / for the final two symbols. URL-safe Base64, or base64url, replaces them with - and _ respectively, because + and / have special meaning in URLs and filenames. URL-safe Base64 also frequently omits the trailing = padding entirely.
The failure happens when a string encoded with one alphabet is decoded with the other. JWTs, OAuth tokens, and many web APIs use base64url, so if you feed a JWT segment to a standard decoder, the - and _ characters are flagged as invalid. The reverse also fails. The fix is to normalize the alphabet before decoding:
import base64
def decode_anyalphabet(s: str) -> bytes:
s = s.strip().replace("-", "+").replace("_", "/")
s += "=" * (-len(s) % 4)
return base64.b64decode(s)
Python also ships base64.urlsafe_b64decode(), which accepts - and _ directly but still requires correct padding, so combine it with the padding rule from Cause 1. If you are decoding a token, our companion piece on how Base64 encoding works explains why these two alphabets exist and when each is used.
A reliable diagnostic order
When you hit a decode error, work through the causes in this sequence rather than guessing:
- Check
length mod 4. If it is 1, the data is truncated; recover the source. If it is 2 or 3, add padding. - Strip whitespace and verify there are no disguised characters before assuming the payload is bad.
- Try the opposite alphabet. Swap
-_for+/(or vice versa) and decode again.
The browser tool short-circuits all of this: paste the string, try standard Decode, then URL-Safe Decode, and watch which mode returns clean output. The decoder trims surrounding whitespace automatically, so you can immediately tell whether your problem is the alphabet, the padding, or genuinely corrupt data.
Frequently Asked Questions
It means your Base64 string's length is not a multiple of four because trailing equals signs were stripped. Re-add padding with the expression s += "=" * (-len(s) % 4) before calling base64.b64decode(). The one exception is a length of 4n+1, which signals truncated data that padding cannot repair.
atob() is strict and rejects any character outside the standard Base64 alphabet, including newlines, spaces, and the URL-safe characters - and _. Strip whitespace and convert URL-safe characters to + and / first. Homoglyphs (look-alike Unicode characters) pasted from styled text also trigger this error.
Look at the last two symbols of the alphabet. Standard Base64 (RFC 4648) uses + and /, while URL-safe Base64 (base64url) uses - and _ instead. URL-safe strings also often drop the trailing = padding. JWTs and OAuth tokens use the URL-safe variant.
The string likely contains invisible or look-alike characters. Hidden whitespace such as tabs, non-breaking spaces, or zero-width characters, and homoglyphs from Cyrillic or Greek code points render identically to valid letters but are not in the alphabet. Use a whitespace visualizer or homoglyph detector to reveal them.
It means the data was truncated, not that padding is missing. A single leftover Base64 character cannot encode any complete bytes, so no valid Base64 string ever has a length of the form 4n+1. Adding equals signs will not fix it; you must recover the missing characters from the source.