Codificações de texto explicadas: UTF-8, Base64 e codificação de URL
Computadores guardam e enviam bytes, não letras. Toda vez que um texto é salvo, enviado ou colocado num endereço da web, algo precisa decidir quais bytes representam quais caracteres, e às vezes os próprios bytes precisam ser reescritos para chegar inteiros. Três codificações fazem quase todo esse trabalho: o UTF-8 transforma caracteres em bytes, o Base64 transforma quaisquer bytes em letras e números comuns, e a codificação por porcentagem deixa um texto seguro dentro de uma URL.
Esta última parte da série explica cada uma com exemplos que dá para conferir byte a byte, e liga tudo aos problemas vistos nas partes sobre JSON e CSV: a marca de ordem de bytes e os acentos que viram ã.
Caracteres, pontos de código e bytes
O Unicode dá a cada caractere um número, chamado ponto de código e escrito como U+ seguido de dígitos hexadecimais: A é U+0041, é é U+00E9, € é U+20AC e o emoji 😀 é U+1F600. A faixa vai de U+0000 a U+10FFFF. Uma codificação decide como esses números são guardados em bytes.
O UTF-8, definido no RFC 3629 (padrão da internet desde novembro de 2003), usa de um a quatro bytes por caractere. Os 128 caracteres ASCII mantêm seu único byte, então um texto em inglês sem acentos é idêntico em ASCII e em UTF-8; os outros caracteres ocupam dois, três ou quatro bytes:
A → 41Um byte, igual ao ASCII.é → C3 A9Dois bytes. Lidos um de cada vez como Windows-1252, aparecem como é.€ → E2 82 ACTrês bytes.😀 → F0 9F 98 80Quatro bytes, como todo caractere acima de U+FFFF.
Hoje o UTF-8 é o padrão em quase todo lugar: o W3Techs o encontrou em 99,1% dos sites cuja codificação conseguiu identificar em outubro de 2026. Mesmo assim, outras codificações continuam existindo dentro dos programas. As strings do JavaScript, por exemplo, são guardadas em UTF-16, em unidades de 16 bits, e o MDN observa que um caractere acima de U+FFFF ocupa duas. É por isso que o mesmo emoji conta como 1 caractere na tela, 2 no comprimento do JavaScript e 4 bytes em UTF-8, e por isso limites medidos em caracteres e em bytes raramente batem.
A marca de ordem de bytes
A marca de ordem de bytes (BOM), U+FEFF, pode ficar logo no início de um arquivo. Em UTF-8 ela é sempre os três bytes EF BB BF. O UTF-8 não tem ordem de bytes para marcar, então ali ela só funciona como uma assinatura que diz que o arquivo é UTF-8. O Consórcio Unicode desaconselha seu uso quando um protocolo ou programa espera caracteres específicos no início do arquivo.
As duas partes anteriores da série esbarram nela por lados opostos. Um JSON enviado pela rede não pode começar com ela, e o JSON.parse do JavaScript rejeita o texto que começa assim. Já o Excel precisa dela para abrir corretamente um CSV em UTF-8 com dois cliques. Colocar ou não depende do programa que vai ler o arquivo.
Base64: quaisquer bytes como texto
Alguns lugares só aceitam texto: o corpo de um e-mail, uma string JSON, um atributo HTML. O Base64, especificado no RFC 4648 (outubro de 2006), permite que quaisquer bytes, inclusive uma imagem ou um PDF, passem por eles. Ele pega a entrada de três em três bytes (24 bits) e escreve cada grupo como quatro caracteres de um alfabeto de 64: A–Z, a–z, 0–9, + e /. Quando o último grupo fica incompleto, o = completa. Estes são os vetores de teste do RFC:
f → Zg==Um byte: dois caracteres de dados e dois = de preenchimento.fo → Zm8=Dois bytes: três caracteres e um =.foo → Zm9vTrês bytes completam um grupo inteiro: quatro caracteres, sem preenchimento.
Quatro caracteres para cada três bytes significa que o Base64 fica um terço maior que o original: um arquivo de 3.000.000 de bytes vira 4.000.000 de caracteres. O e-mail (MIME) ainda quebra o Base64 em linhas de no máximo 76 caracteres, o que acrescenta um pouco mais.
Base64 é uma codificação, não uma criptografia. Qualquer pessoa consegue decodificar, sem chave, então ele não protege nada. Se uma senha ou um token só parecem ilegíveis porque estão em Base64, eles não estão protegidos.
O texto precisa virar bytes antes de ser codificado em Base64, então o resultado depende da codificação do texto. A letra é dá w6k= a partir dos seus dois bytes em UTF-8, mas 6Q== a partir do seu único byte em Latin-1. O web.dev aponta que a função btoa() do próprio navegador só aceita caracteres que cabem em um byte e dá erro com os outros, como €; é preciso converter o texto em bytes UTF-8 antes. A ferramenta de Base64 do nTools codifica o texto como UTF-8 e, ao decodificar, informa se os bytes eram UTF-8 ou Windows-1252.
base64url: a versão para endereços da web
Dois dos caracteres do Base64 dão problema fora do texto comum. Numa URL, o + pode ser lido como espaço e a / separa trechos do caminho; num nome de arquivo, a / não é permitida. Por isso o RFC 4648 define um segundo alfabeto, seguro para URLs e nomes de arquivo, normalmente chamado de base64url, que usa - e _ no lugar deles. As especificações que o usam também podem deixar de fora o preenchimento =, o que o RFC 4648 permite quando elas dizem isso explicitamente.
😀 → 8J+YgA== / 8J-YgABase64 padrão e depois base64url: o + vira - e o preenchimento some.??? → Pz8/ / Pz8_A / vira _.
Os JSON Web Tokens são o exemplo mais conhecido. O RFC 7515 define a codificação deles como base64url sem nenhum = no final e sem quebras de linha, e é por isso que as partes de um token podem ter - e _, mas nunca +, / ou =. Um decodificador que só conhece o Base64 padrão rejeita essas strings; a ferramenta do nTools é um deles, então troque - por + e _ por / antes de decodificar.
Codificação por porcentagem: texto dentro de uma URL
Uma URL só pode ter um conjunto limitado de caracteres ASCII, e alguns têm uma função: a / separa o caminho, o ? abre a consulta, o & separa parâmetros, o # abre o fragmento. O RFC 3986 (janeiro de 2005, padrão da internet) define as regras. Letras, números e - . _ ~ podem sempre ser usados como estão. Os caracteres com função são codificados quando fazem parte de um valor e não servem de separador, e qualquer byte fora do conjunto permitido é escrito como % seguido de dois dígitos hexadecimais. O texto é convertido para UTF-8 antes, então um caractere fora do ASCII vira vários códigos %:
a b&c=d → a%20b%26c%3DdCodificado como um valor só: o espaço, o & e o = não dividem mais a consulta.café → caf%C3%A9Os dois bytes UTF-8 de é, cada um como código %.😀 → %F0%9F%98%80Quatro bytes, quatro códigos %.100% → 100%25Um % literal também precisa ser codificado, como %25, senão o decodificador o lê como início de um código.
Codifique os valores que você coloca numa URL, não o endereço inteiro: codificar https://example.com/?q=1 como um valor só transforma : / ? e = em códigos, o que está certo quando o endereço é ele mesmo um parâmetro de outra URL e errado quando ele precisa funcionar como link. A função encodeURIComponent() do JavaScript faz exatamente essa codificação por valor e, como lista o MDN, também deixa ! ' ( ) e * sem codificar. A ferramenta de URL do nTools usa essa função.
Um + ou %20?
Formulários HTML enviam os dados num formato um pouco diferente, application/x-www-form-urlencoded, definido pelo padrão URL do WHATWG. Nele, um espaço é escrito como +, então uma busca por a b&c=d é enviada como q=a+b%26c%3Dd. No resto de uma URL, o + é só um sinal de mais.
Isso explica uma confusão comum: decodificar a+b%20c com um decodificador de URL comum dá a+b c, porque ali só %20 é espaço; um decodificador de formulário dá a b c. Escolha o decodificador de acordo com a origem do texto. O MDN sugere trocar %20 por + depois de encodeURIComponent() quando você precisa do formato de formulário.
Codificação dupla
O RFC 3986 avisa que uma string não deve ser codificada ou decodificada por porcentagem mais de uma vez. Quando isso acontece, o % de cada código é codificado de novo como %25:
a b → a%20b → a%2520bCodificado duas vezes: depois de uma decodificação o texto ainda mostra a%20b em vez de a b.
Sequências como %2520 ou %25C3%25A9 num endereço indicam que algo codificou um texto que já estava codificado. Decodifique uma vez, confira o resultado e codifique só as partes que precisam.
Qual você precisa?
| Situação | Use |
|---|---|
| Salvar ou enviar texto: arquivos, páginas da web, JSON, CSV | UTF-8 |
| Colocar bytes, como uma imagem ou um arquivo, dentro de um texto: JSON, e-mail, HTML | Base64 |
| Base64 dentro de uma URL, de um nome de arquivo ou de um token | base64url |
| Um valor dentro de uma URL: um termo de busca, um parâmetro, um trecho do caminho | Codificação por porcentagem |
| Dados enviados por um formulário HTML | Codificação de formulário (espaços como +) |
| Manter dados em segredo | Nenhuma delas: use criptografia |
Perguntas frequentes
Base64 é um tipo de criptografia?
Não. É uma codificação reversível que qualquer pessoa decodifica sem chave. Ela deixa os dados prontos para viajar como texto, não protegidos contra leitura.
Por que meu Base64 é diferente do de outra ferramenta?
Normalmente porque o texto foi convertido em bytes com outra codificação, como Latin-1 em vez de UTF-8, porque uma ferramenta usa base64url ou deixa de fora o preenchimento =, ou porque uma delas acrescenta quebras de linha.
Por que um emoji conta como dois caracteres?
Muitos programas, entre eles o JavaScript, contam unidades UTF-16 de 16 bits, e um emoji como 😀, acima de U+FFFF, precisa de duas. Em UTF-8 ele ocupa quatro bytes.
Devo salvar arquivos em UTF-8 com BOM?
Só quando o programa que lê o arquivo precisa, como o Excel com arquivos CSV. Para JSON e a maioria dos arquivos da web, salve em UTF-8 sem BOM.
Por que meu link tem %2520?
Ele foi codificado duas vezes: o %20, um espaço, foi codificado de novo e o % dele virou %25. Decodifique uma vez e use o resultado.
Os codificadores do nTools enviam meu texto para algum lugar?
Não. A codificação e a decodificação acontecem no seu navegador e o texto não é enviado para lugar nenhum.
Anterior da série ← Arquivos CSV explicados: separadores, aspas, codificação e Excel
Referências
- RFC 3629 — UTF-8, a transformation format of ISO 10646
- Unicode Consortium — FAQ: UTF-8, UTF-16, UTF-32 & BOM
- W3Techs — Usage statistics of character encodings for websites
- MDN — UTF-16 (glossary)
- RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format
- Microsoft Support — Opening CSV UTF-8 files correctly in Excel
- RFC 4648 — The Base16, Base32, and Base64 Data Encodings
- web.dev — The nuances of base64 encoding strings in JavaScript
- RFC 7515 — JSON Web Signature (JWS)
- RFC 3986 — Uniform Resource Identifier (URI): Generic Syntax
- WHATWG — URL Standard
- MDN — encodeURIComponent()
Guias relacionados
- Endereços IP explicados: IPv4, IPv6 e as faixas reservadas
- Sub-redes e CIDR explicados: prefixos, máscaras e hosts utilizáveis
- Registros DNS explicados: A, AAAA, CNAME, MX, TXT e NS
- Um site está fora do ar ou é só com você? Como verificar, passo a passo
- Códigos de status HTTP e erros da Cloudflare: o que significam 403, 404, 429, 500, 503 e 521
- Timestamps Unix e fusos horários explicados
- Contagem de datas: dias entre datas, anos bissextos e semanas ISO
- Formatos de imagem explicados: JPEG, PNG, WebP, AVIF e HEIC
- O tamanho de uma imagem explicado: pixels, DPI, peso e qualidade
- Fotos de celular explicadas: HEIC, metadados EXIF e sua privacidade
- Força de senhas explicada: comprimento, entropia e frases-senha
- Funções de hash explicadas: SHA-256, checksums e hash de senhas
- JSON Web Tokens explicados: claims, assinaturas e validação segura
- UUIDs explicados: v4, v7 e como escolher a versão certa
- QR codes explicados: capacidade, correção de erros e tamanho de impressão
- Porcentagens e IVA explicados: somar, tirar e acumular
- Erros comuns em JSON e como corrigi-los
- Arquivos CSV explicados: separadores, aspas, codificação e Excel
- Edição de PDFs no navegador: o que se mantém ao juntar, dividir e converter