Enviar um arquivo
  1. base64hub
  2. Aprenda Base64
  3. Base64 vs Base64URL
Guia

Base64 vs Base64URL: alfabetos, preenchimento e armadilhas

O Base64URL é o Base64 com dois caracteres trocados para que a saída possa ir numa URL ou num nome de arquivo sem alteração. Os bytes são idênticos; os alfabetos diferem em duas posições.

A diferença em uma tabela

As duas variantes estão definidas na RFC 4648. A seção 4 descreve o Base64 padrão, e a seção 5, o alfabeto “URL and Filename safe”. Elas compartilham 62 caracteres e o mesmo algoritmo; só os índices 62 e 63 mudam.

Base64 padrão (RFC 4648 §4)Base64URL (RFC 4648 §5)
Índices 0–61A–Z a–z 0–9iguais
Índice 62+-
Índice 63/_
Preenchimento= obrigatório por padrãogeralmente omitido
Seguro em caminho ou query de URLnãosim
Seguro como nome de arquivonão (/ separa diretórios)sim

Aqui está a mesma entrada codificada dos dois jeitos:

EntradaPadrãoBase64URLBase64URL, sem preenchimento
<<???>>PDw/Pz8+Pg==PDw_Pz8-Pg==PDw_Pz8-Pg
bytes fb ff+/8=-_8=-_8
ManTWFuTWFuTWFu

A última linha lembra que muitas strings ficam idênticas nas duas variantes. Só dá para distinguir uma string se ela contém +, /, - ou _, ou se falta o preenchimento. Quando não dá para saber, o mais seguro é um decodificador que aceite os dois alfabetos.

Um jeito rápido de identificar uma string que caiu nas suas mãos:

  • Contém - ou _: Base64URL.
  • Contém + ou /: Base64 padrão.
  • O comprimento não é múltiplo de 4 e não há =: sem preenchimento, o que quase sempre significa Base64URL.
  • Contém + e - ao mesmo tempo, ou / e _: alguém misturou os alfabetos, ou os dados nem são Base64. O validador de Base64 mostra quais caracteres estão fora do lugar.
  • Tem três partes separadas por pontos começando com eyJ: é um JWT, que por definição é Base64URL.

Por que o Base64URL existe

Numa URL, / separa segmentos do caminho, + significa espaço em query strings codificadas como formulário, e = separa chaves de valores. O Base64 padrão usa os três. Colocá-lo numa URL exige codificação percentual, então ab+/8= vira ab%2B%2F8%3D, que é mais longo, mais feio e mais um passo para dar errado.

A RFC 4648 §5 resolve isso escolhendo - e _, que não são reservados em URLs e são válidos em nomes de arquivo em todos os sistemas de arquivos comuns. A RFC também observa que o preenchimento pode ser omitido quando o comprimento é conhecido, já que o próprio = precisaria de escape como %3D. Mais sobre isso no guia de preenchimento.

Onde cada variante é usada

Base64 padrão:

  • Anexos de e-mail MIME e cabeçalhos codificados (Base64 no e-mail)
  • Certificados e chaves PEM
  • URIs data: em HTML e CSS
  • Headers de HTTP Basic Auth
  • Secrets do Kubernetes e a maioria das APIs JSON que transportam arquivos

Base64URL, geralmente sem preenchimento:

  • JSON Web Tokens: header, payload e assinatura são todos Base64URL sem preenchimento (Base64 no JWT)
  • Chaves JWK, JWS e JWE em geral (RFC 7515–7518)
  • IDs de credencial e challenges do WebAuthn
  • Valores code_challenge do OAuth PKCE (RFC 7636)
  • Tokens aleatórios para URLs, como o secrets.token_urlsafe() do Python, que retornou uma string de 43 caracteres para 32 bytes aleatórios

Como converter entre eles

A conversão é pura substituição de caracteres, com o preenchimento tratado à parte. Os bytes nunca mudam.

Do padrão para URL-safe: troque + por -, / por _ e remova os = do final. De URL-safe para o padrão: troque - por +, _ por / e acrescente = até o comprimento ser múltiplo de 4.

const toUrl = (b64) =>
  b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

const fromUrl = (u) => {
  const b64 = u.replace(/-/g, "+").replace(/_/g, "/");
  return b64 + "=".repeat((4 - (b64.length % 4)) % 4);
};

toUrl("PDw/Pz8+Pg==");   // "PDw_Pz8-Pg"
fromUrl("PDw_Pz8-Pg");   // "PDw/Pz8+Pg=="

A maioria das linguagens também traz um codificador direto, então raramente é preciso fazer esse malabarismo com strings:

LinguagemCodificador Base64URLPreenchimento
Pythonbase64.urlsafe_b64encode(b)mantido; remova com .rstrip(b"=")
Node.jsbuf.toString("base64url")omitido
Gobase64.RawURLEncoding / base64.URLEncodingomitido / mantido
JavaBase64.getUrlEncoder().withoutPadding()omitido
PHPrtrim(strtr(base64_encode($s), '+/', '-_'), '=')omitido

Na decodificação, o Buffer.from(s, "base64") do Node aceita os dois alfabetos e o preenchimento ausente. O base64.urlsafe_b64decode do Python precisa que o preenchimento seja restaurado antes. O atob() do navegador rejeita - e _ com um InvalidCharacterError, então converta antes ou use Uint8Array.fromBase64 com alphabet: "base64url" onde estiver disponível. Código para outras linguagens está nas páginas de código, por exemplo Python e Java.

Se você só precisa converter uma string, Base64 para Base64URL e Base64URL para Base64 fazem isso no navegador.

Armadilhas

O + vira espaço

Esse é o bug mais comum. Base64 padrão colocado numa query string sem escape é estragado pela decodificação de formulário, que lê + como espaço:

from urllib.parse import parse_qs
parse_qs("t=ab+/8=")
# {'t': ['ab /8=']}
new URLSearchParams("t=ab+/8=").get("t");
// "ab /8="

O decodificador seguinte então falha no espaço, ou o ignora e devolve os bytes errados. A correção é usar Base64URL, ou encodeURIComponent() na ida. Como conserto rápido do lado de quem recebe, trocar os espaços de volta por + costuma funcionar, porque Base64 nunca contém espaços.

Misturar alfabetos sem perceber

O base64.b64decode do Python descarta caracteres fora do alfabeto padrão, a menos que você passe validate=True. Se receber uma string Base64URL, ele joga fora os - e _ e decodifica o resto:

import base64
base64.b64decode("PDw_Pz8-Pg==")
# b'<<\x0f\xcf\xc3\xe0'   (wrong; the input was "<<???>>")
base64.b64decode("PDw_Pz8-Pg==", validate=True)
# binascii.Error: Only base64 data is allowed
base64.urlsafe_b64decode("PDw_Pz8-Pg==")
# b'<<???>>'

A primeira chamada devolve bytes errados sem levantar nenhum erro. Use urlsafe_b64decode para entrada URL-safe, ou validate=True para que a incompatibilidade fique evidente.

Dupla codificação

Duas coisas diferentes recebem esse nome, e as duas acontecem na prática.

Codificação percentual duas vezes. Uma string Base64 padrão recebe escape uma vez no seu código e mais uma vez num cliente HTTP, então + vira %2B e depois %252B. O servidor decodifica uma vez e recebe um %2B literal. O Base64URL evita o problema porque não há nada para escapar.

Base64 de Base64. Um valor é codificado em duas camadas, por exemplo pela aplicação e de novo por um framework ou pelas ferramentas de manifesto do Kubernetes. Man vira TWFu e depois VFdGdQ==. O sinal é que a decodificação devolve algo que continua sendo feito de caracteres Base64. Decodifique de novo e veja se o resultado faz sentido.

Expectativas sobre preenchimento

Alguns receptores exigem preenchimento e outros o rejeitam. O RawURLEncoding do Go falha com entrada que contém =, enquanto o decodificador do Python falha com entrada sem ele. Ao definir uma API, documente a variante e a regra de preenchimento, e faça seu próprio decodificador aceitar as duas formas.

Decodificar Base64URL

Cole uma string URL-safe, com ou sem preenchimento, e veja o que tem dentro.

Decodificador Base64URL

Perguntas frequentes

Base64URL é o mesmo que aplicar URL encoding numa string Base64?

Não. A codificação percentual transforma + em %2B e / em %2F, o que deixa a string mais longa. O Base64URL usa outro alfabeto justamente para que nenhum escape seja necessário.

Um decodificador Base64 padrão consegue ler Base64URL?

Não de forma confiável. Alguns decodificadores, como o Buffer do Node, aceitam os dois alfabetos. Outros rejeitam - e _, e o b64decode do Python os descarta silenciosamente por padrão, o que produz bytes errados.

O Base64URL tem preenchimento?

A RFC 4648 permite, e o urlsafe_b64encode do Python o mantém. A maioria dos formatos que usam Base64URL, incluindo o JWT, descarta o = porque o comprimento já o determina.

Qual devo usar para tokens em URLs?

Base64URL sem preenchimento. Ele não precisa de escape em caminhos, query strings, cookies nem nomes de arquivo.

Continue lendo