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.