Error messages at a glance
| Message | Where you see it | Most likely cause |
|---|---|---|
binascii.Error: Incorrect padding | Python base64.b64decode | Missing = at the end, often from Base64URL or a cut-off string |
binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4 | Python | Truncated input, or -/_ silently dropped |
binascii.Error: Only base64 data is allowed | Python with validate=True | Newline, space, - or _ in the input |
InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded. | Chrome, Edge | Base64URL characters, junk characters, bad length |
InvalidCharacterError: String contains an invalid character | Firefox | Same as above |
InvalidCharacterError: Invalid character | Node.js atob | Same as above |
IllegalArgumentException (“Illegal base64 character …”) | Java java.util.Base64 | Wrong decoder for the alphabet, or line breaks |
FormatException | .NET Convert.FromBase64String | Base64URL characters or missing padding |
base64: invalid input | GNU coreutils base64 -d | Characters outside the alphabet |
Fixing decoder errors
“Incorrect padding” in Python
Base64 output always has a length that is a multiple of 4, padded with = at the end. Python’s decoder requires the padding:
>>> import base64
>>> base64.b64decode("SGVsbG8")
Traceback (most recent call last):
...
binascii.Error: Incorrect padding
>>> base64.b64decode("SGVsbG8=")
b'Hello'
Missing padding usually means the string is Base64URL (JWTs, URL tokens and many APIs drop =), or something trimmed it. Add the padding back:
s = "SGVsbG8"
s += "=" * (-len(s) % 4)
When the length is one more than a multiple of 4, padding cannot help. Python then reports number of data characters (5) cannot be 1 more than a multiple of 4. A single leftover character holds only 6 bits, so part of the data is missing. You can also hit this message with a string that is not truncated: unless you pass validate=True, Python discards characters outside the alphabet, including - and _. base64.b64decode("SGk_Pg==") drops the _ and ends up with 5 data characters. Use base64.urlsafe_b64decode for Base64URL input.
“Invalid character” and atob failures
Browsers implement atob with the same algorithm but word the error differently. Chrome says “The string to be decoded is not correctly encoded”, Firefox says “String contains an invalid character”, Node.js says “Invalid character”. All three throw an InvalidCharacterError for these inputs:
- Base64URL characters.
atob("SGk_Pg")fails because of_. Replace-with+and_with/first. - A length of 4n+1 after whitespace is removed, such as
atob("A"). - Characters that do not belong: quotes copied from JSON, a
data:prefix, a%2Ffrom URL encoding.
atob does forgive some things. It skips spaces and newlines, and it accepts missing padding when the length works out, so atob("SGVsbG8") returns "Hello". The mirror error, btoa failing with “characters outside of the Latin1 range”, is about Unicode text and is covered in Uint8Array.toBase64.
A tolerant decoder that handles both alphabets, whitespace and missing padding:
function decodeLoose(s) {
s = s.replace(/\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");
s += "=".repeat((4 - (s.length % 4)) % 4);
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
}
new TextDecoder().decode(decodeLoose("SGk_Pg")); // "Hi?>"
The Python equivalent:
import base64
def b64decode_any(s: str) -> bytes:
s = "".join(s.split())
s = s.replace("-", "+").replace("_", "/")
s += "=" * (-len(s) % 4)
return base64.b64decode(s, validate=True)
The Repair Base64 tool applies the same cleanup and tells you what it changed.
“Illegal base64 character” in Java and FormatException in .NET
Java’s java.util.Base64 ships three decoders, and each one is strict about its own alphabet. The message names the offending character in hex: 5f is _, 2d is -, 2f is /, 20 is a space, a is a newline.
| Input contains | Use |
|---|---|
+ and / | Base64.getDecoder() |
- and _ | Base64.getUrlDecoder() |
| Line breaks (MIME, PEM bodies) | Base64.getMimeDecoder() |
The basic decoder rejects line breaks and spaces. It does accept missing padding, so SGVsbG8 decodes without complaint.
.NET’s Convert.FromBase64String throws FormatException for Base64URL characters and for missing padding. It skips spaces and line breaks. Fix the alphabet and padding as shown above before calling it. See Base64 in Java and Base64 in C# for complete examples.
The text comes out garbled
Decoding succeeded, but the text looks wrong. Base64 stores bytes, and the bytes were produced from text with some character encoding. Read them back with a different one and you get mojibake.
| You see | Original | What happened |
|---|---|---|
Café | Café | UTF-8 bytes read as Latin-1 or Windows-1252 |
Caf� | Café | Windows-1252 bytes read as UTF-8 |
H e l l o or H\0e\0l\0l\0o\0 | Hello | UTF-16LE bytes read as UTF-8 |
The first row is the classic atob trap. atob("Q2Fmw6k=") returns "Café", because atob maps each byte to one character. Decode the bytes as UTF-8 instead:
const bytes = Uint8Array.from(atob("Q2Fmw6k="), (c) => c.charCodeAt(0));
new TextDecoder().decode(bytes); // "Café"
UTF-16LE is common with Windows tools: PowerShell’s -EncodedCommand and many .NET APIs produce it. Base64 to text lets you switch the charset until the output reads correctly, and Base64 to UTF-16 handles the wide-character case.
The output looks like binary
If the decoded result is full of �, control characters or PNG, PK and %PDF fragments, the payload is a file. Look at the first characters of the Base64: iVBORw0KGgo is a PNG, JVBERi0 a PDF, H4sI gzip. The full list is in common Base64 prefixes. Open the data with Base64 to file, which detects the format and offers a download. For H4sI use decode gzip Base64.
Decoding gives different bytes than expected
No error, but the bytes are wrong. These are the usual reasons:
- A trailing newline at encoding time.
echo "hello" | base64givesaGVsbG8K(with\n), whileecho -n "hello" | base64givesaGVsbG8=. Useprintf '%s'orecho -n. +turned into a space. In URL query strings and form bodies,+means space.URLSearchParams("t=PGI+Ym9sZDwvYj4=").get("t")returnsPGI Ym9sZDwvYj4=. Some decoders then throw, others drop the space and produce garbage. Percent-encode Base64 withencodeURIComponentbefore putting it in a URL, or use Base64URL.- Percent-encoding left in place.
SGk%2FPg%3D%3Dmust be URL-decoded toSGk/Pg==before Base64 decoding. - Escapes copied from JSON. Copying a string out of raw JSON can bring in a literal
\n(two characters). Python’s lenient decoder drops the backslash, keeps then, and returns wrong bytes without an error:b64decode("SGVs\\nbG8=")givesb'Hel\x9d\xb1\xbc'. Parse the JSON instead of copying from it. The JSON Base64 decoder does this for every field. - Strict versus lenient whitespace. Python without
validate=True,atob, .NET and the Java MIME decoder skip line breaks. Python withvalidate=Trueand the Java basic decoder reject them. MIME and PEM data is wrapped at 76 or 64 characters, so pick a decoder that accepts it.
Data that was encoded twice
Decoding U0dWc2JHOGdkMjl5YkdRaA== gives SGVsbG8gd29ybGQh, which is again valid Base64 and decodes to Hello world!. When the result of one decode consists only of Base64 characters, with a length that is a multiple of 4, suspect double encoding.
It often happens with Kubernetes Secrets. Values under data: are Base64, and kubectl create secret --from-literal encodes for you. If you encode the value yourself first, the pod receives Base64 text instead of the password. Use stringData: for plain values, or check what is stored with the Kubernetes Secret decoder. When in doubt, the Base64 validator shows whether a string is valid and how many bytes it holds.
FAQ
How do I get rid of binascii.Error: Incorrect padding?
Append = until the length is a multiple of 4: s += "=" * (-len(s) % 4). If the string is Base64URL, also replace - with + and _ with /, or use base64.urlsafe_b64decode.
Why does atob fail on a JWT?
JWTs use Base64URL, which has - and _. atob only knows + and /. Convert the characters first, or paste the token into the JWT decoder.
Why does my decoded text show é instead of é?
The bytes are UTF-8 but were read as Latin-1 or Windows-1252. Decode the bytes as UTF-8, for example with TextDecoder in JavaScript or .decode("utf-8") in Python.
Can a Base64 string with a length of 4n+1 be repaired?
No. One leftover character carries only 6 bits, less than a byte, so at least one character is missing. Find the original source or check where the string was cut.
How can I tell if data was Base64-encoded twice?
Decode it once. If the result is again a string of only A-Z a-z 0-9 + / = with a length that is a multiple of 4, decode it again.