Enviar um arquivo
  1. base64hub
  2. Aprenda Base64
  3. Codificação vs criptografia
Segurança

Base64 não é criptografia: codificação vs criptografia

O Base64 só muda a forma como os bytes são escritos. Tudo o que você codifica em Base64 pode ser decodificado por qualquer um que veja, com um comando e sem chave.

Três operações diferentes

O Base64 aparece com frequência em lugares onde as pessoas esperam proteção, então vale ser preciso sobre o que ele faz. Ele é uma codificação: um mapeamento fixo e público de bytes para texto (veja como o Base64 funciona). O mapeamento não tem chave nem segredo, e a mesma entrada sempre produz a mesma saída.

Codificação (Base64, hex)Criptografia (AES-GCM)Hash (SHA-256, Argon2)
FinalidadeRepresentar bytes como textoManter os dados confidenciaisGerar uma impressão digital ou verificar dados
ReversívelSim, por qualquer umSim, com a chaveNão
Precisa de chaveNãoSimNão (o HMAC usa uma)
Mesma entrada, mesma saídaSempreNão, com um nonce aleatórioSim, a menos que use salt
Tamanho da saídaCerca de 4/3 da entradaEntrada mais nonce e tagComprimento fixo

Decodificar não exige esforço nenhum:

echo ZGVwbG95Omh1bnRlcjI= | base64 -d
# deploy:hunter2

Essa string parece opaca para quem bate o olho. Para um script, um scanner de logs ou um atacante, é texto comum com um passo a mais.

Um teste útil: se um valor seria um problema num print de tela, ele continua sendo um problema depois de codificado em Base64.

Como dados em Base64 vazam na prática

O padrão é sempre o mesmo: um valor é codificado em Base64 para transporte, alguém o confunde com algo protegido, e ele acaba num lugar onde pode ser lido. Estas são as formas comuns desse erro.

Headers de HTTP Basic Auth

A autenticação Basic (RFC 7617) envia o usuário e a senha como Base64(user:password) em toda requisição:

Authorization: Basic ZGVwbG95Omh1bnRlcjI=

A única proteção é o TLS na conexão. Assim que o header sai desse túnel, a senha está, na prática, em texto puro. Formas típicas de isso acontecer: proxies reversos ou frameworks de aplicação que registram os headers das requisições em log, arquivos HAR exportados das ferramentas de desenvolvedor do navegador e anexados a um chamado de suporte, saída de curl -v colada num chat e ferramentas de rastreamento de erros que capturam requisições completas. O decodificador de Basic Auth mostra como é pouco o necessário para ler um.

Secrets do Kubernetes

Um Secret do Kubernetes guarda seus valores codificados em Base64:

apiVersion: v1
kind: Secret
metadata:
  name: db
data:
  password: czNjcjN0

czNjcjN0 é s3cr3t. A codificação existe para que os valores possam conter dados binários dentro do YAML. A documentação do Kubernetes afirma que, por padrão, os Secrets ficam armazenados sem criptografia no etcd, e qualquer um com permissão de get no Secret pela API consegue lê-lo. Os vazamentos comuns são manifestos com valores data: reais enviados a um repositório Git, saída de kubectl get secret -o yaml colada em chamados e backups do etcd ou do estado do cluster guardados sem criptografia. O decodificador de Secrets do Kubernetes decodifica todos os valores de um Secret de uma vez, o que também é um jeito rápido de conferir o que um manifesto realmente expõe.

As credenciais de registros de contêineres seguem o mesmo padrão. O campo auth num config.json do Docker ou num Secret .dockerconfigjson é Base64(user:password); o decodificador de dockerconfigjson o lê.

Payloads de JWT

Um JWT assinado parece um token lacrado, mas as duas primeiras partes são só JSON em Base64URL:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImVtYWlsIjoiYW5hQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIn0.<signature>

O payload decodifica para {"sub":"42","email":"ana@example.com","role":"admin"}. A assinatura protege o token contra alterações; ela não faz nada pela confidencialidade. Toda extensão de navegador, proxy, arquivo de log e script de analytics que vê o token consegue ler o que há dentro. Dados pessoais, IDs internos que você prefere não expor e qualquer coisa secreta não pertencem ao payload de um JWT. Se precisam viajar num token, use um JWT criptografado (JWE). Mais detalhes em Base64 no JWT.

Segredos “ofuscados” em código e configuração

Chaves de API guardadas em Base64 no JavaScript do front-end, em pacotes de apps mobile ou em arquivos de configuração são um clássico. O raciocínio de sempre é que isso evita a chave ficar “em texto puro”. Evita, sim, que ela apareça numa busca ingênua pela chave literal, e só. Tudo o que é entregue a um cliente pode ser lido por esse cliente.

A mesma ideia corre no sentido contrário em ataques. O -EncodedCommand do PowerShell recebe Base64 de texto UTF-16LE, e é muito usado para deixar comandos maliciosos mais difíceis de ler à primeira vista. Os defensores o decodificam em segundos, por exemplo com o decodificador de comandos codificados do PowerShell.

O que usar no lugar

Escolha a ferramenta pelo trabalho. O Base64 ainda tem lugar em cada um destes casos, como o invólucro de texto em volta de bytes protegidos por outra coisa.

Para manter dados confidenciais: criptografia autenticada

Use AES-256-GCM ou ChaCha20-Poly1305, por meio de uma biblioteca bem mantida. As duas são autenticadas, então qualquer adulteração é detectada na descriptografia. Duas regras importam mais que o algoritmo: nunca reutilize um nonce com a mesma chave, e guarde a chave em outro lugar que não ao lado dos dados (um KMS, um gerenciador de segredos ou um ambiente que o atacante não controle também).

import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";

const key = randomBytes(32);   // in real code, load this from a KMS or secret store
const iv = randomBytes(12);    // 96-bit nonce, unique per message

const cipher = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([cipher.update("deploy:hunter2", "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();

// Base64 carries the encrypted bytes as text: nonce + tag + ciphertext
const token = Buffer.concat([iv, tag, ct]).toString("base64");

const raw = Buffer.from(token, "base64");
const d = createDecipheriv("aes-256-gcm", key, raw.subarray(0, 12));
d.setAuthTag(raw.subarray(12, 28));
console.log(Buffer.concat([d.update(raw.subarray(28)), d.final()]).toString("utf8"));
// deploy:hunter2

A saída é uma string Base64 de 56 caracteres que muda a cada execução, porque o nonce é aleatório. Sem a chave, ela é inútil. Para experimentar sem escrever código, as ferramentas criptografar AES e descriptografar AES rodam no seu navegador.

Para armazenar senhas: um hash lento e com salt

Senhas não devem ser criptografadas nem codificadas. Guarde um hash gerado por uma função feita para senhas: Argon2id, scrypt ou bcrypt, com um salt aleatório único por senha. Um SHA-256 simples de uma senha é rápido demais de calcular e cai por força bruta. A biblioteca padrão do Python inclui o scrypt:

import hashlib, hmac, os

def hash_password(pw: str) -> bytes:
    salt = os.urandom(16)
    return salt + hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)

def check_password(pw: str, stored: bytes) -> bool:
    salt, digest = stored[:16], stored[16:]
    candidate = hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)
    return hmac.compare_digest(candidate, digest)

Se você precisa colocar o resultado numa coluna de texto ou em JSON, codifique-o em Base64.

Para provar integridade: assinaturas e HMAC

Quando o objetivo é “ninguém alterou isto” em vez de “ninguém consegue ler isto”, use um HMAC com um segredo compartilhado ou uma assinatura digital. O resultado são bytes, normalmente transportados como Base64 ou hex. O gerador de HMAC mostra a saída nas duas formas.

Para credenciais em trânsito e em repouso

Use TLS em toda conexão que carregue Basic Auth ou tokens, prefira tokens de curta duração a senhas estáticas, remova os headers Authorization dos logs, ative a criptografia em repouso para os Secrets do Kubernetes e mantenha os valores reais de segredos fora do Git.

Perguntas frequentes

Base64 é seguro?

O Base64 não tem nenhuma propriedade de segurança. Não existe chave, então decodificar não exige nada além da própria string. Ele é seguro como formato de transporte para dados que já estão protegidos, como texto cifrado ou uma assinatura.

Base64 serve para esconder uma senha num arquivo de configuração?

Não. Qualquer um que consiga ler o arquivo decodifica a senha com base64 -d. Restrinja as permissões do arquivo, use um gerenciador de segredos ou criptografe o valor com uma chave guardada em outro lugar.

Por que os Secrets do Kubernetes são codificados em Base64 se não é por segurança?

Para que os valores de um Secret possam conter dados binários arbitrários, como chaves TLS ou keystores, dentro de YAML e JSON. A proteção vem do RBAC, da criptografia em repouso do etcd e do cuidado com os manifestos.

É preciso o segredo para ler o payload de um JWT?

Não. O header e o payload são JSON em Base64URL que qualquer um consegue ler. A chave só é necessária para verificar ou criar a assinatura. Experimente com o decodificador de JWT.

Qual a diferença entre codificação, criptografia e hash?

A codificação pode ser revertida por qualquer um. A criptografia só pode ser revertida com uma chave. O hash é de mão única: você pode conferir um valor contra um hash, mas não consegue transformar o hash de volta no valor.

Continue lendo