"A gente pode só passar em Base64 antes de guardar." Se você revisa arquivos de configuração, dashboards internos, bundles de navegador, handlers de webhook ou código de autenticação suficientes, cedo ou tarde vê essa frase. Soa plausível porque a saída parece embaralhada:
c2VjcmV0IHBhc3N3b3Jk
Mas Base64 não é criptografia. Não é proteção com senha. Não é um jeito seguro de esconder chaves de API, claims de JWT, IDs de usuário, credenciais de Basic Auth ou dados privados numa URL. O mesmo valor decodifica em uma única chamada:
atob("c2VjcmV0IHBhc3N3b3Jk");
// "secret password"
A regra prática é dura: se o valor original volta sem chave secreta, Base64 não protegeu nada. Só mudou a representação.
O teste rápido de segurança
Quando ver Base64 no código, pergunte-se que trabalho ele está fazendo:
| Base64 está sendo usado para... | Bom uso? | O que verificar |
|---|---|---|
| Encaixar bytes em JSON | Sim | Manter limites de tamanho razoáveis |
Embutir uma data: URI pequena |
Sim | Não inlinar arquivos grandes às cegas |
| Guardar ciphertext, nonce, sal ou bytes de assinatura como texto | Sim | O passo criptográfico tem que vir antes do Base64 |
| Carregar um cursor de paginação opaco | Normalmente | Trate como detalhe de interface, não como controle de acesso |
| Construir um cabeçalho Basic Auth | Só com HTTPS | Quem protege a requisição é o TLS, não o Base64 |
| Esconder uma chave de API no frontend | Não | Todo navegador recebe o valor e o decodificador |
| Guardar uma senha | Não | Use hashing lento de senhas |
| Esconder claims de JWT | Não | Payloads de JWT assinados são legíveis, a menos que sejam cifrados como JWE |
| Esconder IDs ou papéis de usuário em URLs | Não | A autorização no servidor ainda precisa rodar |
Se a frase que vem à cabeça é "pra ninguém conseguir ler", você não está descrevendo Base64. Está descrevendo criptografia, hashing, assinatura, controle de acesso ou gestão de segredos.
O que o Base64 realmente faz
Base64 é um esquema de codificação de binário para texto. O RFC 4648 define o alfabeto Base64 comum como:
A-Z a-z 0-9 + / =
O caractere = é padding. Base64 agrupa os bytes de entrada em blocos de 24 bits: 3 bytes de entrada viram 4 caracteres imprimíveis. É por isso que a saída Base64 fica cerca de 33 % maior que os bytes brutos antes de qualquer compressão.
btoa("hello");
// "aGVsbG8="
atob("aGVsbG8=");
// "hello"
O mesmo round trip funciona no terminal:
printf '%s' 'secret password' | base64
# c2VjcmV0IHBhc3N3b3Jk
printf '%s' 'c2VjcmV0IHBhc3N3b3Jk' | base64 -d
# secret password
Sem prompt de senha, sem chave privada, sem passo de descriptografia, sem sal, nonce, IV, tag nem work factor. Quem tem a string codificada consegue reverter.
Base64 vs Base64url
Base64 padrão nem sempre é URL-safe porque +, / e = são desconfortáveis em URLs, nomes de arquivo, cookies e segmentos de token. Base64url muda o alfabeto:
| Característica | Base64 padrão | Base64url |
|---|---|---|
| Caracteres 62 e 63 | + e / |
- e _ |
| Padding | Geralmente = |
Frequentemente omitido |
| Onde aparece | MIME, PEM, Basic Auth, campos binários | JWTs, tokens em URL, nomes de arquivo |
| Diferença de segurança | Nenhuma | Nenhuma |
Isso importa quando você debuga JWTs. Um JWT usa Base64url, não Base64 padrão com padding:
header.payload.signature
Se você cola um segmento de JWT num decoder Base64 padrão e falha, a string não é necessariamente secreta nem cifrada. Talvez só precise de tratamento Base64url:
function base64urlToBase64(part) {
let out = part.replace(/-/g, "+").replace(/_/g, "/");
while (out.length % 4 !== 0) out += "=";
return out;
}
Essa conversão troca o alfabeto. Não adiciona confidencialidade.
UTF-8: a armadilha do navegador
btoa() e atob() do navegador operam sobre strings binárias. Exemplos em ASCII puro funcionam de boa:
btoa("hello");
// "aGVsbG8="
Mas texto Unicode precisa de um passo em bytes antes. Isso pega de surpresa equipes que testam com hello e depois codificam nomes de cliente, chinês, emoji ou árabe:
btoa("你好");
// InvalidCharacterError in browsers
Use bytes UTF-8:
function utf8ToBase64(text) {
const bytes = new TextEncoder().encode(text);
const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
return btoa(binary);
}
function base64ToUtf8(base64) {
const binary = atob(base64);
const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
const encoded = utf8ToBase64("你好");
base64ToUtf8(encoded);
// "你好"
A ferramenta Base64 Encode & Decode deste site faz o round trip UTF-8 no navegador, então texto não-ASCII volta como texto e não como mojibake.
Por que devs confundem Base64 com criptografia
Base64 costuma aparecer perto de segurança de verdade:
- Certificados TLS ficam guardados em texto PEM.
- JWTs usam seções Base64url.
- HTTP Basic Auth usa Base64 em
username:password. - Chaves públicas SSH contêm material de chave codificado em Base64.
- Ciphertext criptografado normalmente é codificado em Base64 antes de storage ou transporte.
Esse último ponto é o que mais confunde. Dados criptografados podem estar codificados em Base64, mas o Base64 não é a criptografia. A segurança veio de uma operação criptográfica como AES-GCM, XChaCha20-Poly1305, RSA-OAEP, JWE ou TLS. O Base64 só fez os bytes resultantes caberem em JSON, e-mail, cabeçalhos ou coluna de banco.
Use palavras precisas no código e no texto de produto:
encodedBackup // OK se for só Base64
encryptedBackup // Só OK se houve criptografia de verdade
signedState // OK se um HMAC ou assinatura protege integridade
passwordHash // OK se um algoritmo de hashing de senha produziu
Nomes aqui não são cosmética. Um nome enganoso pode fazer um futuro revisor supor uma fronteira de segurança que não existe.
Codificação vs criptografia vs hashing vs assinatura
Essas operações resolvem problemas diferentes:
| Operação | Papel principal | Precisa de segredo? | Reversível? | Exemplo |
|---|---|---|---|---|
| Codificação | Mudar a representação | Não | Sim | Base64, Base64url, URL encoding |
| Criptografia | Esconder o texto claro | Sim | Sim, com chave | AES-GCM, XChaCha20-Poly1305, JWE |
| Hashing | Impressão digital de mão única | Não | Não | SHA-256 para checksums |
| Hashing de senha | Verificação lenta de senha | Sal e parâmetros de custo | Não | Argon2id, bcrypt, scrypt |
| Assinatura / MAC | Provar integridade e origem | Sim, ou par de chaves | Não | HMAC, JWS, assinatura Ed25519 |
Base64 responde à pergunta "essa sequência de bytes consegue passar por um canal de texto?". Não responde a "quem pode ler isso?", "quem alterou isso?" nem "esse usuário pode acessar aquele registro?".
Erro real de segurança 1: chaves de API escondidas no frontend
Esse padrão aparece em apps React, extensões de navegador, temas WordPress e ferramentas internas de admin:
// A chave chega em todos os navegadores mesmo assim.
const apiKey = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");
fetch("https://api.example.com/report", {
headers: { Authorization: `Bearer ${apiKey}` },
});
Base64 não ajuda porque o navegador recebe tanto o valor codificado quanto o decodificador. Qualquer pessoa abre o DevTools, inspeciona o bundle JavaScript, roda o mesmo atob() e copia a chave.
O conserto é arquitetural:
- Guarde segredos reais no servidor.
- Dê às chaves de navegador escopos estreitos e restrições por domínio.
- Faça proxy das chamadas sensíveis a upstream pelo seu backend.
- Rotacione qualquer segredo que já foi entregue aos usuários.
- Assuma que scanners de segredos decodificam strings parecidas com Base64.
Codificar uma chave pode complicar o code review. Não deixa a chave mais segura.
Erro real de segurança 2: senhas guardadas como Base64
É prejudicial porque pode ficar calado num banco por anos:
// Armazenamento reversível. Não faça isso.
const storedPassword = btoa(password);
// Depois:
const password = atob(storedPassword);
Se o banco vazar, cada senha volta na hora. Não tem custo de crackeamento porque não tem nada pra crackear.
Armazenamento de senha deve ser de mão única:
// Formato do fluxo, usando uma biblioteca de hashing de senha.
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, candidatePassword);
Sistemas de login normais nunca deveriam precisar recuperar a senha do usuário. Só precisam verificar uma senha candidata contra um hash guardado.
Erro real de segurança 3: Basic Auth confundido com criptografia
O cabeçalho parece codificado o suficiente para enganar:
Authorization: Basic dXNlcjpwYXNzd29yZA==
Ele decodifica para:
atob("dXNlcjpwYXNzd29yZA==");
// "user:password"
HTTP Basic Auth só é aceitável quando a requisição está protegida por HTTPS. TLS criptografa a troca HTTP em trânsito. Base64 só empacota username:password numa string ASCII compatível com cabeçalho.
Sobre HTTP puro, um observador de rede consegue ler as credenciais. Em logs, proxies reversos, traces de APM e dumps de suporte, um cabeçalho Basic Auth deve ser tratado como senha em texto claro.
Erro real de segurança 4: payloads de JWT tratados como privados
Um JWT assinado normal tem três seções Base64url:
header.payload.signature
As duas primeiras decodificam para JSON. A terceira é a assinatura.
eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIn0
Decodifica para:
{
"sub": "1234",
"role": "admin"
}
Isso não quer dizer que o token seja falso. Um JWT assinado pode ser à prova de adulteração e ainda assim legível. A assinatura protege a integridade: diz ao servidor se o token foi emitido por uma parte confiável e se o cabeçalho ou o payload mudaram. Ela não esconde o payload.
Não coloque senhas, chaves de API, segredos de sessão, números completos de cartão, detalhes médicos ou dados privados de perfil no payload de um JWT assinado normal. Se claims precisam de confidencialidade, use JWE ou evite pôr esses dados no token.
Além disso: decodificar não é verificar. Uma ferramenta de dev mostra os claims; um servidor ainda precisa verificar assinatura, issuer, audience, expiração, key ID, política de algoritmo e as regras de autorização específicas da aplicação antes de confiar.
Erro real de segurança 5: parâmetros de URL que parecem opacos
Apps legadas costumam passar estado assim:
/profile?data=eyJ1c2VySWQiOjQyfQ==
Isso decodifica para:
{
"userId": 42
}
Dois problemas surgem. Qualquer um lê e qualquer um edita e recodifica. Se /profile?data=... é a única coisa que decide qual registro de usuário carregar, o bug é autorização faltando.
Padrões mais seguros:
- Trate dados de URL decodificados como input não confiável.
- Reveja permissões no servidor.
- Prefira uma referência curta e aleatória no servidor quando o estado for sensível.
- Use HMAC ou assinatura se o cliente precisa carregar estado à prova de adulteração.
- Mantenha estado privado fora das URLs, porque URLs vão parar em logs, histórico de navegador, analytics e screenshots de suporte.
Erro real de segurança 6: exports "criptografados" que só estão codificados
Outro cheiro de code review é uma função assim:
function exportEncryptedBackup(data) {
return btoa(JSON.stringify(data));
}
O nome da função fala em criptografado. O código fala em codificado.
Se o export só precisa ser um blob de texto portátil, renomeie:
function exportBase64Backup(data) {
return btoa(JSON.stringify(data));
}
Se você está dizendo aos usuários que o export é criptografado, use criptografia autenticada de verdade e deixe o manejo de chaves explícito. Isso significa uma biblioteca de cripto revisada, nonces/IVs aleatórios, tags de autenticação, geração de chave segura e um plano de armazenamento de chaves. O Base64 ainda pode envolver o ciphertext depois.
Para que Base64 realmente serve
Base64 é útil. O erro é atribuir a ele um papel de segurança.
Bons usos:
- Dados binários dentro de JSON, XML, HTML ou e-mail.
- Anexos de e-mail MIME.
data:URIs pequenas para imagens ou fontes.- Formatação de saída criptográfica para ciphertext, assinaturas, nonces, sais e chaves públicas.
- Segmentos de serialização compacta JWT e JOSE.
- Cursores de paginação opacos, onde legibilidade é preocupação de produto e não fronteira de segurança.
- Debug de campos de API, payloads JWT e blobs de configuração.
A formulação segura é "codificado em Base64", não "criptografado". Essa palavra sozinha evita uma quantidade surpreendente de mal-entendidos em produção.
O que usar no lugar
Se você foi buscar Base64 porque queria segurança, escolha a primitiva que combina com o trabalho:
| Objetivo | Use no lugar |
|---|---|
| Esconder dados de usuários ou atacantes | Criptografia autenticada como AES-GCM ou XChaCha20-Poly1305 |
| Proteger dados em trânsito | HTTPS/TLS |
| Guardar senhas | Argon2id, bcrypt ou scrypt com sais por senha e custo ajustado |
| Guardar chaves de API | Cofre de segredos, ambiente do servidor, escopos, logs de auditoria e rotação |
| Provar que um valor não mudou | HMAC ou assinatura digital |
| Impedir que usuários editem estado na URL | Autorização no servidor mais estado assinado ou referência de sessão no servidor |
| Manter claims de JWT privados | JWE, ou não pôr claims privados no token |
| Enfiar binário em JSON, e-mail ou cabeçalho | Base64 está correto |
A parte difícil raramente é o nome do algoritmo. É modelagem de ameaças, gestão de chaves, rotação, controle de acesso e decidir quem deveria conseguir ver o texto claro em primeiro lugar.
Um checklist prático de code review
Procure os usos de Base64:
rg "btoa\\(|atob\\(|base64|Buffer\\.from\\(.*base64|toString\\('base64'\\)"
Classifique cada ocorrência:
- Transporte: conversão de bytes para texto em JSON, MIME, data URI, PEM ou storage. Geralmente ok.
- Formatação depois de cripto: ciphertext, nonce, assinatura, sal ou chave pública. Geralmente ok se a cripto está certa.
- Interface opaca: cursor, código de convite ou estado carregado pelo cliente. Verifique autorização e proteção contra adulteração.
- Esconder segredo: chave de API, senha, token, credencial, claim privado ou dados do usuário. Não ok.
- Nomeação enganosa: variáveis ou textos de UI dizem criptografado, seguro, protegido, secreto, mascarado ou escondido. Corrija o nome ou a implementação.
Depois pergunte:
- Um navegador, app mobile, usuário, coletor de logs, proxy ou ferramenta de suporte receberia o valor codificado?
- Tem um decodificador logo ao lado?
- Um servidor verifica permissões após decodificar?
- O valor está assinado se o cliente pode editar?
- Se é um JWT, a assinatura é verificada antes de qualquer decisão de confiança?
- Se é senha, por que é reversível?
Isso mantém a revisão com os pés no chão. Um PNG numa data URI é chato. Uma chave de API de produção num bundle JavaScript não é.
Resposta a incidente: se você achar "segredos" em Base64
Se você descobrir um segredo codificado em Base64 em código-fonte, logs, um ticket, analytics, uma URL ou um export de banco:
- Decodifique localmente para confirmar o que é.
- Trate o valor decodificado como exposto.
- Rotacione credenciais, tokens e chaves que possam ter ficado visíveis.
- Remova a cópia codificada de código, logs, screenshots ou tickets quando der.
- Substitua o design por segredo no servidor, chave pública com escopo, estado assinado, criptografia real ou hash de senha.
- Adicione uma regra de revisão para o mesmo padrão não voltar.
Não gaste tempo discutindo se a string Base64 era "difícil de perceber". Scanners automatizados, usuários do navegador e atacantes conseguem decodificar do mesmo jeito.
Quando você realmente precisa de um token criptografado
Um JWT assinado padrão usa JWS: payload legível, assinatura à prova de adulteração. Se você precisa mesmo de confidencialidade dos claims, use JWE (JSON Web Encryption).
Um JWE compacto tem cinco seções Base64url:
protected-header.encrypted-key.iv.ciphertext.authentication-tag
Essas seções continuam sendo texto Base64url. A confidencialidade vem do algoritmo de criptografia do JWE e da chave do destinatário, não do Base64url em si.
Em muitas apps web, um design mais simples é melhor: guarde o estado sensível no servidor e mande ao navegador só um identificador de sessão curto e aleatório. Nem todo JWT legível precisa virar JWE.
Perguntas frequentes
Base64 é criptografia?
Não. Base64 é codificação reversível sem chave secreta. Qualquer pessoa com a string decodifica usando uma função de navegador, um comando de terminal, uma ferramenta online ou algumas linhas de código.
Base64 pelo menos serve como ofuscação?
Só no sentido mais fraco possível. Pode esconder o valor de um olhar apressado, mas não vai deter um desenvolvedor, atacante, scanner, extensão de navegador, leitor de log ou ferramenta de suporte.
Base64 é seguro para senhas ou chaves de API?
Não. Senhas pedem hashing lento como Argon2id, bcrypt ou scrypt. Chaves de API devem ficar no servidor ou num cofre de segredos com escopo, auditoria e rotação.
Qualquer um consegue ler o payload de um JWT?
Sim, para JWTs assinados comuns. Cabeçalho e payload são JSON codificado em Base64url. A assinatura pode provar integridade depois da verificação, mas não esconde os claims.
Por que o Basic Auth usa Base64 se não é seguro?
Basic Auth usa Base64 pra fazer username:password caber num cabeçalho HTTP. HTTPS/TLS entrega a criptografia em trânsito. Sem HTTPS, as credenciais Basic Auth ficam praticamente em claro na rede.
Base64url é mais seguro que Base64?
Não. Base64url troca + e / por - e _ e costuma omitir o padding para o valor funcionar em URLs e JWTs. Continua sendo codificação reversível.
O que devo usar no lugar de Base64 pra criptografar?
Um design de criptografia autenticada revisado, como AES-GCM, XChaCha20-Poly1305 ou JWE, junto com geração de chave, armazenamento de chave, rotação e controle de acesso decentes. Não desenhe o seu próprio formato criptográfico.
Para que Base64 serve mesmo?
Base64 representa bytes como texto: campos binários em JSON, anexos de e-mail, data URIs, arquivos PEM, assinaturas, formatação de ciphertext e segmentos de token URL-safe. É um invólucro, não uma fronteira de segurança.
Codifique e decodifique no seu navegador
Precisa inspecionar uma string Base64 agora? Base64 Encode & Decode na fixjson.org permite codificar ou decodificar texto localmente no navegador, incluindo conteúdo UTF-8. Use para inspecionar payloads JWT, campos de resposta de API ou data: URIs, mas não confunda decodificar com verificar ou descriptografar.
Ferramentas e guias relacionados
- Base64 Encode & Decode — codifique e decodifique Base64 localmente no seu navegador.
- Decodifique strings Base64 e payloads JWT — decodificação prática de Base64 e Base64url.
- Como decodificar um JWT — ler claims e entender por que decodificar não é verificar.
- JSON sensível e ferramentas locais — decidir o que é seguro colar em ferramentas de navegador.
- JSON Stringify — inspecionar e escapar strings JSON aninhadas.
- RFC 4648: o padrão Base64 — o pano de fundo formal de Base64 e Base64url.
- RFC 7519: JSON Web Token — estrutura do JWT e considerações de segurança.
Fontes
- RFC 4648 — Base64, Base64url, padding, alfabetos e considerações de segurança.
- RFC 7617 — HTTP Basic Authentication e seu formato de credenciais em Base64.
- RFC 7519 — estrutura do JWT, decisões de confiança e considerações de privacidade.
- RFC 7516 — JSON Web Encryption e serialização compacta JWE.
- MDN btoa e MDN atob — primitivas Base64 do navegador e pegadinhas de UTF-8.
- OWASP Cryptographic Storage Cheat Sheet — criptografia, modos autenticados e gestão de chaves.
- OWASP Password Storage Cheat Sheet — orientações sobre hashing de senha.
Última revisão em julho de 2026.