← Todos os artigos

Base64 não é criptografia: o que ele esconde e o que não

Base64 é codificação, não criptografia. Por que senhas, payloads JWT, chaves de API, cabeçalhos Basic Auth, URLs e logs seguem legíveis após Base64.

"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:

  1. Transporte: conversão de bytes para texto em JSON, MIME, data URI, PEM ou storage. Geralmente ok.
  2. Formatação depois de cripto: ciphertext, nonce, assinatura, sal ou chave pública. Geralmente ok se a cripto está certa.
  3. Interface opaca: cursor, código de convite ou estado carregado pelo cliente. Verifique autorização e proteção contra adulteração.
  4. Esconder segredo: chave de API, senha, token, credencial, claim privado ou dados do usuário. Não ok.
  5. 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:

  1. Decodifique localmente para confirmar o que é.
  2. Trate o valor decodificado como exposto.
  3. Rotacione credenciais, tokens e chaves que possam ter ficado visíveis.
  4. Remova a cópia codificada de código, logs, screenshots ou tickets quando der.
  5. Substitua o design por segredo no servidor, chave pública com escopo, estado assinado, criptografia real ou hash de senha.
  6. 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

Fontes

Última revisão em julho de 2026.