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
| Onde | O 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 2 | Invalid 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
| Entrada | atob | Python b64decode | .NET | PHP estrito |
|---|---|---|---|---|
Sem preenchimento: SGVsbG8 | Decodifica | Incorrect padding | Falha | Decodifica |
Preenchimento a mais: SGVsbG8== | Falha | Decodifica | Falha | Devolve false |
| Espaços ou quebras de linha | Ignorados | Ignorados (falha com validate=True) | Ignorados | Ignorados |
- e _ do Base64URL | Falha | Descartados sem aviso: bytes errados ou um erro | Falha | Devolve false |
Outros caracteres: aspas, \, !, : | Falha | Descartados sem aviso | Falha | Devolve 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_encodedo PHP escreve/como\/, e conteúdo quebrado em linhas, como um certificado PEM, é guardado com\nentre 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 comoaGVsbG8=, que por sua vez virahello. Decodifique de novo. - Letras acentuadas aparecem como
é. Oatobdevolve 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
UnicodeDecodeErrordepois 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 0xe9para 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.