binascii.Error: Incorrect padding (base64, tested)
binascii.Error: Incorrect padding from base64.b64decode() means the = padding was stripped, usually by a JWT, URL token or API. We measured the fix on Python 3.13.5, plus a trap: padding base64url input and using the standard decoder silently drops bytes.
1. What “Incorrect padding” actually means
Base64 encodes 3 bytes as 4 characters. When the input length isn't a multiple of 3, the encoder pads with = so the output length is a multiple of 4. Many producers (JWTs, URL tokens, some APIs) strip the =. Python's decoder wants it back:
import base64, binascii
for s in ["aGVsbG8", "aGVsbG8=", "aGVsbA", "aGVsbA==", "aGVsb"]:
try:
print(repr(s), "->", base64.b64decode(s))
except binascii.Error as e:
print(repr(s), "-> binascii.Error:", e)
Output:
'aGVsbG8' -> binascii.Error: Incorrect padding
'aGVsbG8=' -> b'hello'
'aGVsbA' -> binascii.Error: Incorrect padding
'aGVsbA==' -> b'hell'
'aGVsb' -> binascii.Error: Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4
Measured on Python 3.13.5: 7 and 6 characters raise Incorrect padding; adding the missing = decodes them. 5 characters is a different error: a length of 4n+1 can never be valid base64, so padding won't help. That string is truncated or corrupted.
2. Fix: add the missing padding
import base64
def b64decode_padded(s):
s = s.strip()
return base64.b64decode(s + "=" * (-len(s) % 4))
for s in ["aGVsbG8", "aGVsbA", "aGVsbG8="]:
print(repr(s), "->", b64decode_padded(s))
Output:
'aGVsbG8' -> b'hello'
'aGVsbA' -> b'hell'
'aGVsbG8=' -> b'hello'
-len(s) % 4 is the number of = needed (0 to 3). It's safe on already-padded input, as the third line shows.
3. JWTs and URL tokens: padding and the URL-safe alphabet
import base64, binascii
# JWT segments are base64url without padding
seg = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkrDtnJnIn0"
for name, fn in [("b64decode", base64.b64decode), ("urlsafe_b64decode", base64.urlsafe_b64decode)]:
try:
print(name, fn(seg))
except binascii.Error as e:
print(name, "binascii.Error:", e)
padded = seg + "=" * (-len(seg) % 4)
print("urlsafe + padding:", base64.urlsafe_b64decode(padded).decode())
Output:
b64decode binascii.Error: Incorrect padding
urlsafe_b64decode binascii.Error: Incorrect padding
urlsafe + padding: {"sub":"1234567890","name":"Jörg"}
Measured: a JWT payload segment fails with Incorrect padding in both decoders, because JWTs never carry =. With padding added, urlsafe_b64decode works. Don't stop at padding with the standard decoder, though:
import base64
data = b"\xfb\xff\xbfOK"
token = base64.urlsafe_b64encode(data).decode().rstrip("=") # how JWTs and URLs carry it
padded = token + "=" * (-len(token) % 4)
print("token: ", token)
print("b64decode: ", base64.b64decode(padded))
print("urlsafe_b64decode:", base64.urlsafe_b64decode(padded))
Output:
token: -_-_T0s
b64decode: b'OK'
urlsafe_b64decode: b'\xfb\xff\xbfOK'
Measured: after padding, b64decode did not raise. It returned b'OK', silently dropping the first 3 bytes, because - and _ aren't in the standard alphabet and the default decoder discards characters it doesn't know (see b64decode silently strips invalid characters). Base64url input needs urlsafe_b64decode.
4. Inputs that decode to the wrong bytes without any error
import base64, binascii
from urllib.parse import unquote_plus
original = b"\xfb\xef\xbehi!"
s = base64.b64encode(original).decode()
print("encoded:", s)
for label, v in [("data URL prefix", "data:text/plain;base64," + base64.b64encode(b"hello world").decode()),
("+ became space (form/query decoding)", unquote_plus(s)),
("line breaks (email, PEM)", s[:4] + "\n" + s[4:])]:
try:
print(f"{label}: {v!r} -> {base64.b64decode(v)}")
except binascii.Error as e:
print(f"{label}: {v!r} -> binascii.Error: {e}")
print("validate=True:", end=" ")
try:
base64.b64decode(unquote_plus(s), validate=True)
except binascii.Error as e:
print("binascii.Error:", e)
Output:
encoded: ++++aGkh
data URL prefix: 'data:text/plain;base64,aGVsbG8gd29ybGQ=' -> b'u\xabZ\xb5\xecm\xfe\x99Z\x8av\xda\xb1\xee\xb8hello world'
+ became space (form/query decoding): ' aGkh' -> b'hi!'
line breaks (email, PEM): '++++\naGkh' -> b'\xfb\xef\xbehi!'
validate=True: binascii.Error: Only base64 data is allowed
Measured: a data: URL prefix decodes to 15 garbage bytes followed by the real content. Base64 that went through form or query-string decoding (+ becomes a space) silently lost its first 3 bytes. Line breaks are harmless. validate=True turns the silent cases into an error. Strip the data:...;base64, prefix and URL-encode base64 before putting it in a query string.
Frequently asked questions
How do I fix binascii.Error: Incorrect padding in Python?
Add the missing = characters: base64.b64decode(s + '=' * (-len(s) % 4)). We measured this fixing 7- and 6-character inputs and leaving padded input unchanged.
Why does decoding a JWT give Incorrect padding?
JWT segments are base64url with the = padding stripped. Add padding and use base64.urlsafe_b64decode; we measured the standard decoder silently dropping bytes on - and _.
What does 'number of data characters cannot be 1 more than a multiple of 4' mean?
The string length is 4n+1, which no valid base64 has. Padding can't fix it; the data was truncated or corrupted.
Can base64.b64decode return wrong data without an error?
Yes. We measured a data: URL prefix producing garbage bytes and a + turned into a space losing 3 bytes, both with no error. Pass validate=True to make them raise.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: unpadded inputs of 5, 6 and 7 characters, the padding fix, a JWT payload segment, base64url input through both decoders, and data-URL, plus-to-space and line-break inputs with and without validate=True. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.