String Base64 inválida: por que a decodificação falha e como corrigir

Um decodificador Base64 rejeita uma string por um de três motivos: um caractere fora do alfabeto, um comprimento impossível ou preenchimento no lugar errado.

O Base64 usa 64 caracteres, de A a Z, de a a z, de 0 a 9, + e /, além de = como preenchimento (padding) no final (RFC 4648, seção 4). Qualquer outra coisa faz um decodificador estrito falhar. A parte confusa é que os decodificadores não concordam sobre o que entra nesse "qualquer outra coisa": uma string Base64URL como PDw_Pz8-Pg== é decodificada corretamente pelo Buffer do Node.js, falha no .NET e vira bytes errados, sem nenhum aviso, no Python. Saber o que o seu decodificador tolera já é a maior parte da solução.

O erro em cada linguagem

OndeO que você recebe
Chrome e Edge, atob()InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
Node.js, atob()InvalidCharacterError: Invalid character, ou The string to be decoded is not correctly encoded. quando o comprimento está errado
Python, base64.b64decode()binascii.Error: Incorrect padding, Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4 ou, com validate=True, Only base64 data is allowed
.NET, Convert.FromBase64String()FormatException: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.
Java, Base64.getDecoder().decode()IllegalArgumentException: Illegal base64 character seguido do código do caractere em hexadecimal, como 2d para -
PHP, base64_decode($s, true)Nenhuma mensagem: a função devolve false
AWS CLI versão 2Invalid base64: seguido do valor que você passou

O Buffer.from(s, 'base64') do Node.js é a exceção: ele nunca lança erro. Ele pula os caracteres que não reconhece, então uma string danificada gera bytes danificados sem erro nenhum.

O que cada decodificador tolera

EntradaatobPython b64decode.NETPHP estrito
Sem preenchimento: SGVsbG8DecodificaIncorrect paddingFalhaDecodifica
Preenchimento a mais: SGVsbG8==FalhaDecodificaFalhaDevolve false
Espaços ou quebras de linhaIgnoradosIgnorados (falha com validate=True)IgnoradosIgnorados
- e _ do Base64URLFalhaDescartados sem aviso: bytes errados ou um erroFalhaDevolve false
Outros caracteres: aspas, \, !, :FalhaDescartados sem avisoFalhaDevolve false

O modo padrão do Python merece um alerta. Ele descarta todos os caracteres fora do alfabeto antes de decodificar, então as letras de um prefixo como data:image/jpeg;base64, são decodificadas como se fossem dados. Dependendo do tamanho do prefixo, você recebe um erro confuso sobre o número de caracteres, ou nenhum erro e vários bytes de lixo na frente do conteúdo real. Passe validate=True quando quiser que uma entrada ruim falhe de forma clara.

Caracteres que não deveriam estar ali

Quando um decodificador estrito acusa um caractere inválido, a string quase sempre pegou alguma coisa no caminho até você:

  • Aspas ou texto ao redor. Copiar "SGVsbG8=" com as aspas, ou uma linha inteira "key": "value", acrescenta caracteres que não são Base64.
  • Um prefixo de data URI. Em data:image/png;base64,iVBOR..., só a parte depois da vírgula é Base64. Remova tudo até a vírgula, incluindo a própria vírgula.
  • Escapes de JSON. O json_encode do PHP escreve / como \/, e conteúdo quebrado em linhas, como um certificado PEM, é guardado com \n entre as linhas. Se você copia o texto JSON bruto em vez do valor já interpretado, as barras invertidas vêm junto. Faça o parse do JSON primeiro e decodifique a string resultante.
  • Caracteres do Base64URL. - e _ indicam que a string usa o alfabeto seguro para URLs, como fazem os JWTs. Veja a seção mais abaixo.
  • Caracteres invisíveis. Um espaço não separável ou um espaço de largura zero colado de um app de mensagens ou de um documento não é um espaço em branco ASCII, então até o atob, que ignora espaços comuns, o rejeita.

Um caso não gera erro nenhum. Se um valor em Base64 padrão vai para uma query string sem codificação percentual, cada + vira espaço quando a query é interpretada. O atob e o Python pulam esses espaços: ++++YWJj chega como YWJj e é decodificado como abc, com os três primeiros bytes perdidos sem aviso. Codifique o valor com encodeURIComponent ou use Base64URL em URLs.

Comprimento e preenchimento errados

O Base64 transforma cada 3 bytes em 4 caracteres, então uma string com preenchimento sempre tem comprimento divisível por 4. Sem preenchimento, o último grupo tem 2 ou 3 caracteres. Resto 1 é impossível para qualquer entrada: se length % 4 === 1, um caractere se perdeu ou sobrou um a mais, e nenhuma quantidade de preenchimento resolve isso.

O preenchimento só pode aparecer no final, e nunca há mais de dois sinais =. A RFC 4648 exige preenchimento, a menos que o formato que usa o Base64 diga o contrário, e vários formatos dizem: JWTs e code challenges do PKCE o omitem. Para restaurar o preenchimento que falta antes de entregar a string a um decodificador estrito:

// JavaScript
const padded = s + '='.repeat((4 - s.length % 4) % 4);

# Python
padded = s + '=' * (-len(s) % 4)

Base64URL entregue a um decodificador padrão

O Base64URL (RFC 4648, seção 5) troca + por - e / por _, e normalmente descarta o preenchimento. Um decodificador padrão rejeita esses dois caracteres, mas só falham as strings que por acaso contêm um deles, então o bug parece aleatório. Um valor aleatório de 16 bytes vira 22 caracteres Base64URL, e 21 deles podem ser - ou _. Cada um evita os dois com probabilidade 62/64, então (62/64) elevado a 21, cerca de 51%, desses valores são decodificados sem problema, e os outros 49% falham. Converta o alfabeto e restaure o preenchimento, ou use um decodificador feito para essa variante:

// Browser
const b64 = s.replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));

// Node.js
Buffer.from(s, 'base64url');

# Python
base64.urlsafe_b64decode(s + '=' * (-len(s) % 4))

// Java
Base64.getUrlDecoder().decode(s);

O urlsafe_b64decode do Python continua exigindo o preenchimento, e é por isso que o exemplo o acrescenta. As diferenças entre os dois alfabetos são explicadas no guia Base64 vs Base64URL.

Quebras de linha de e-mails e arquivos PEM

Corpos de e-mail MIME quebram o Base64 em linhas de 76 caracteres, e arquivos PEM, como certificados, em linhas de 64. O atob, o .NET e o PHP ignoram as quebras de linha. O decodificador básico do Java não: use Base64.getMimeDecoder() para entradas quebradas em linhas. No caso do PEM, remova também as linhas -----BEGIN ...----- e -----END ...-----, que não são Base64.

Decodificou, mas o resultado está errado

  • A saída é mais Base64. Os dados foram codificados duas vezes. YUdWc2JHOD0= é decodificado como aGVsbG8=, que por sua vez vira hello. Decodifique de novo.
  • Letras acentuadas aparecem como é. O atob devolve um caractere por byte, então texto em UTF-8 precisa de um segundo passo: new TextDecoder().decode(Uint8Array.from(atob(s), c => c.charCodeAt(0))).
  • O Python lança UnicodeDecodeError depois de decodificar. Os bytes são Base64 válido, mas não formam texto UTF-8, por exemplo 'utf-8' codec can't decode byte 0xe9 para um texto salvo em Latin-1. Decodifique com a codificação certa, como .decode('cp1252'), ou trate o resultado como binário.
  • A saída são símbolos ilegíveis. Os dados são binários, como uma imagem, um PDF ou dados compactados. Salve os bytes em um arquivo em vez de exibi-los na tela.

AWS CLI: "Invalid base64"

A AWS CLI versão 2 espera, por padrão, que os parâmetros binários estejam em Base64. Passar JSON puro para um comando como aws lambda invoke --payload '{"id": 1}' falha com Invalid base64. Acrescente --cli-binary-format raw-in-base64-out para passar o texto como está, ou codifique o payload você mesmo.

Conferir uma string rapidamente

Cole o valor no decodificador Base64. Ele remove espaços em branco e o prefixo de data URI, converte os caracteres Base64URL e acrescenta o preenchimento que falta antes de decodificar; se mesmo assim ele disser que a entrada não é Base64 válido, a string contém de fato um caractere estranho ou tem um comprimento impossível. Quando a decodificação funciona, ele mostra o texto UTF-8 diretamente e oferece qualquer outra coisa, como uma imagem, como arquivo para download.

Perguntas Frequentes

O que significa "Incorrect padding" no Python?

O comprimento da string não é múltiplo de 4 e falta o preenchimento com =, o que é normal em Base64URL e nas partes de um JWT. Acrescente = até o comprimento ser múltiplo de 4. Se a divisão do comprimento por 4 deixar resto 1, a string está danificada.

Por que o atob lança InvalidCharacterError?

A string contém um caractere fora do alfabeto Base64, como - ou _ do Base64URL, uma aspa ou uma barra invertida, ou tem preenchimento a mais ou um comprimento impossível. Espaços e quebras de linha são ignorados e não são a causa.

Por que a mesma string decodifica em uma linguagem e falha em outra?

Os decodificadores têm níveis diferentes de rigor. O Python e o Buffer do Node.js pulam caracteres desconhecidos por padrão, o modo estrito do PHP e o atob aceitam a falta de preenchimento, e o .NET rejeita tanto a falta de preenchimento quanto caracteres desconhecidos.

Como decodificar Base64URL?

Troque - por + e _ por /, acrescente = até o comprimento ser múltiplo de 4 e decodifique normalmente. O Node.js aceita o formato diretamente com Buffer.from(s, 'base64url').