Enviar um arquivo
  1. base64hub
  2. Aprenda Base64
  3. Preenchimento (=)
Explicação

Preenchimento no Base64: para que servem os sinais =

O = no fim do Base64 marca um bloco final curto. Alguns formatos o descartam, alguns decodificadores fazem questão dele, e restaurá-lo exige uma linha de código.

Por que o Base64 precisa de preenchimento

O Base64 converte 3 bytes em 4 caracteres (o algoritmo completo está em como o Base64 funciona). Quando o comprimento da entrada não é múltiplo de 3, o último bloco fica curto:

Bytes no último blocoCaracteres de dadosPreenchimentoExemplo
34nenhumMan → TWFu
23=Ma → TWE=
12==M → TQ==

O = mantém o comprimento da saída múltiplo de 4. Isso facilitava a vida dos decodificadores da era MIME, que podiam ler a entrada em grupos fixos de 4 caracteres e saber onde os dados terminam sem acompanhar o comprimento total.

A rigor, essa informação é redundante. Um grupo final de 3 caracteres só pode significar 2 bytes, e um grupo de 2 caracteres só pode significar 1 byte, então um decodificador que conta caracteres não precisa de = nenhum. É por isso que o Base64 sem preenchimento é possível e por que tantos formatos o usam.

Existe um caso em que o preenchimento carrega informação real: a concatenação. Se duas strings com preenchimento forem coladas, o = no meio marca onde a primeira termina. Mas os decodificadores lidam mal com isso. Passar TQ==TQ== (dois M codificados) para o decodificador padrão do Python retornou b'M\x04\xd0', o Buffer do Node retornou só M, e o atob lançou um erro. Decodifique cada pedaço separadamente.

Quando o preenchimento é opcional

A RFC 4648 §3.2 diz que os codificadores DEVEM acrescentar o preenchimento, a menos que a especificação que usa o Base64 diga explicitamente o contrário. Várias dizem:

  • JWT e JOSE (RFC 7515) usam Base64URL sem o preenchimento. Um header de JWT como eyJhbGciOiJIUzI1NiJ9 não tem = mesmo quando o comprimento não é múltiplo de 4. Veja Base64 no JWT.
  • URLs e nomes de arquivo costumam levar Base64URL sem preenchimento, porque o = tem significado em query strings e precisaria de escape como %3D. A página Base64 vs Base64URL explica a diferença completa.
  • WebAuthn e OAuth PKCE (o code_challenge da RFC 7636) também usam Base64URL sem preenchimento.

O Base64 padrão em e-mail, arquivos PEM, data URIs e na maioria das APIs JSON tem preenchimento. Na dúvida, gere saída com preenchimento para o Base64 padrão e sem preenchimento para o Base64URL, e faça seu decodificador aceitar as duas.

Decodificadores que exigem preenchimento

O comportamento varia mais do que se imagina. Estes são os resultados de decodificar TWE (Ma sem preenchimento) com o decodificador padrão de cada biblioteca:

Linguagem / ferramentaChamadaResultado para TWE
Python 3.13base64.b64decodebinascii.Error: Incorrect padding
Python 3.13base64.urlsafe_b64decodeIncorrect padding também
.NETConvert.FromBase64StringFormatException
Gobase64.StdEncodingerro; use RawStdEncoding para entrada sem preenchimento
Gobase64.RawStdEncodingMa, mas rejeita TWE=
RubyBase64.strict_decode64ArgumentError: invalid base64
RubyBase64.decode64, urlsafe_decode64Ma
Java 21Base64.getDecoder().decodeMa
Node.jsBuffer.from(s, 'base64'), atobMa
PHP 8.4base64_decode (também no modo estrito)Ma
GNU coreutils 9.7base64 -dMa
OpenSSL 3.5openssl base64 -dsaída vazia, código de saída 0

Repare na linha do OpenSSL: ele não imprimiu nada e ainda assim relatou sucesso, então um script que passa Base64 sem preenchimento pelo openssl base64 -d gera um arquivo vazio sem avisar. Nos mesmos testes, ele também não retornou nada para uma única linha sem quebra de linha no final; passar -A resolveu esse caso.

O Go é o mais rígido no sentido oposto: o RawStdEncoding recusa uma string que tem preenchimento. Se você aceita entrada de várias fontes, remova os = e use a codificação raw, ou acrescente o preenchimento e use a padrão.

Exemplos por linguagem estão nas páginas de código, por exemplo Base64 em Python e Base64 em Go.

Como restaurar o preenchimento que falta

A regra depende só do comprimento da string depois de remover os espaços em branco:

len % 4O que acrescentarSignificado
0nadablocos completos
2==o último bloco contém 1 byte
3=o último bloco contém 2 bytes
1não tem consertotruncada, ou um caractere a mais

Resto 1 nunca é válido. Um caractere Base64 carrega 6 bits, o que não basta para um byte, então nenhuma quantidade de preenchimento torna a string decodificável. Se você vir isso, procure um caractere faltando, um caractere a mais ou uma quebra de linha que entrou na contagem.

A correção é uma expressão na maioria das linguagens:

import base64
s = "eyJzdWIiOiIxMjM0In0"            # 19 chars, 19 % 4 == 3
padded = s + "=" * (-len(s) % 4)     # adds one "="
print(base64.urlsafe_b64decode(padded))
# b'{"sub":"1234"}'
const s = "eyJzdWIiOiIxMjM0In0";
const padded = s + "=".repeat((4 - (s.length % 4)) % 4);
// "eyJzdWIiOiIxMjM0In0="

O validador de Base64 informa o comprimento e o estado do preenchimento de uma string, e o Reparar Base64 acrescenta o = que falta, remove espaços em branco e aspas e converte caracteres URL-safe numa só etapa.

Codificações não canônicas

O preenchimento tem um parente mais discreto: os bits não usados do último caractere de dados. Quando Ma é codificado, seus 16 bits ocupam dois grupos de 6 bits e meio. O terceiro caractere carrega os últimos 4 bits de a mais 2 bits de enchimento, e a RFC 4648 §3.5 diz que o codificador deve zerar esses bits. Isso dá E (000100).

Um decodificador ignora os bits de enchimento, então TWF= (em que F é 000101, bits de enchimento 01) decodifica para o mesmo Ma. Python, Node, Java e PHP aceitaram TWF= e retornaram Ma nos nossos testes. Da mesma forma, TR== decodifica para M, assim como TQ==.

Strings como TWF= são chamadas de não canônicas. Elas importam em alguns lugares:

  • Assinaturas e hashes sobre texto Base64. Duas strings diferentes decodificam para os mesmos bytes, então comparar strings Base64 para saber se os dados são iguais pode dar a resposta errada. Compare os bytes decodificados.
  • Deduplicação e chaves de cache. O mesmo problema: normalize decodificando e codificando de novo.
  • Parsers rígidos. A RFC 4648 permite que os decodificadores rejeitem bits de enchimento diferentes de zero. O StdEncoding.Strict() do Go e o base64 -d do GNU rejeitam TWF=.

Preenchimento a mais é uma esquisitice parecida. TWE== tem um = sobrando. O Python e o Buffer do Node aceitaram, e o atob lançou InvalidCharacterError.

Preenchimento ao codificar em pedaços

Se você codifica um arquivo grande em Base64 por partes, todas as partes, menos a última, precisam ter um múltiplo de 3 bytes. Caso contrário, cada parte ganha seu próprio preenchimento e a saída unida fica errada. Codificar hello world!! em pedaços de 4 bytes deu aGVsbA==byB3bw==cmxkIQ==IQ==; em pedaços de 3 bytes deu aGVsbG8gd29ybGQhIQ==, idêntico a codificar tudo de uma vez. Para a quebra de linha do MIME, 57 bytes por linha é o tamanho natural do pedaço, já que 57 bytes viram exatamente 76 caracteres.

Corrija o preenchimento automaticamente

Cole uma string com preenchimento ausente ou quebrado e receba Base64 válido de volta.

Reparar Base64

Perguntas frequentes

Posso simplesmente apagar os sinais = do Base64?

Os dados sobrevivem, porque o comprimento da string já diz ao decodificador quantos bytes o último bloco contém. Se o resultado ainda decodifica depende de quem recebe: o Base64URL dos JWTs espera ausência de preenchimento, enquanto o b64decode do Python e o Convert.FromBase64String do .NET rejeitam a string sem ele.

Por que minha string Base64 não tem = no final?

Ou o comprimento da entrada era múltiplo de 3 bytes, e o preenchimento não foi necessário, ou quem gerou a string usa Base64 sem preenchimento, o que é comum no Base64URL de tokens e URLs.

Como corrigir o erro Incorrect padding no Python?

Acrescente '=' * (-len(s) % 4) antes de decodificar. Se a string for URL-safe, use também base64.urlsafe_b64decode. Se o comprimento mod 4 for 1, o preenchimento não vai ajudar: a string está truncada ou tem um caractere a mais.

Uma string Base64 pode terminar com três sinais =?

Não. Um bloco final contém 1 ou 2 bytes restantes, que produzem == ou =. Três = significariam um bloco sem dados, o que não existe.

Continue lendo