Você tem que lidar com o formato Base64? Então esse site é perfeito para você! Use o nosso ferramenta online super útil para codificar ou decodificar os seus dados.

Decodificação Base64 em Bash: um guia completo

De tempos em tempos, uma string de letras, dígitos e, de vez em quando, +, / ou = cai no seu terminal, e você precisa da coisa de verdade de volta. Um JWT colado num chamado, um arquivo .b64 anexado a um e-mail de suporte, um secret do Kubernetes que parece sopa de letras, um PNG escondido dentro de uma tag data: de HTML. Este é o guia de campo para resgatar os bytes em Bash e no shell, usando as ferramentas que você quase com certeza já tem instaladas.

O formato numa só frase: o Base64 reescreve cada três bytes de dados brutos como quatro caracteres tirados de um alfabeto de 64 letras (A-Z, a-z, 0-9, mais + e /), e adiciona um ou dois sinais de = no final quando a contagem de bytes não é um múltiplo de três. Decodificar é a direção de encolhimento dessa troca: quatro caracteres entram, três bytes saem. A página inicial deste site percorre o formato passo a passo, então este artigo gasta seu tempo onde ele deve, no lado shell do trabalho.

Aqui está a reviravolta: não existe um único comando base64. O nome é compartilhado por um programa C do GNU, uma reescrita em Rust, um applet do BusyBox, um resquício do BSD e uma utilidade do OpenSSL cujas flags se sobrepõem de um jeito genuinamente perigoso. Todos concordam no alfabeto, mas nem sempre concordam sobre como parece uma entrada quebrada, e é nessa diferença que os scripts morrem. Então o primeiro passo é descobrir quem está respondendo quando você digita base64.

Conheça o decodificador com quem você está falando

Um comando conta a maior parte da história:

base64 --version

Dependendo da máquina, você está lidando com uma dessas:

O que você vê O que você tem Flag de decodificação
base64 (GNU coreutils) 9.x A implementação C clássica, ainda o padrão na maioria das distribuições Linux -d ou --decode
base64 (uutils coreutils) 0.8.x A reescrita do coreutils em Rust, o userland padrão nas versões atuais do Ubuntu -d (e, de forma incomum, -D também funciona)
Texto de uso estilo BSD, sem flag de versão O base64 do BSD no macOS e nos BSDs, descendente da antiga ferramenta bintrans -D (nesta família, o -d minúsculo significa debug, não decodificar)
BusyBox v1.x O binário all-in-one do Alpine Linux e dos sistemas embutidos -d

Repare no que falta nessa tabela: OpenSSL. O openssl base64 é outra criatura completamente diferente, e sua flag -d significa descriptografar, não decodificar. Essa única flag é responsável por mais arquivos de saída vazios em silêncio do que qualquer outro hábito deste artigo, então vamos conhecê-la direito na seção de fallback.

Se a sua distribuição empacota mais de uma família lado a lado (a Ubuntu atual faz isso), mais alguns comandos mostram o quadro completo:

command -v base64
base64 --version 2>&1 | head -1

Quatro one-liners que cobrem a maioria dos dias

Decodifique uma string a partir do standard input. Esse é o movimento que você vai fazer mil vezes, e o printf impede que o shell decore o seu payload:

printf '%s' "SGVsbG8sIFdvcmxkIQ==" | base64 -d

Decodifique um arquivo. Toda implementação séria aceita um argumento FILE, e é a forma mais limpa de manter os dados longe da mecânica de aspas do shell:

base64 -d payload.b64 > payload.bin

Decodifique de um here-string. O here-string anexa um newline no final, mas todo decodificador trata newlines como espaço em branco ignorável, então isso é completamente seguro para blobs pequenos:

base64 -d <<< "SGVsbG8sIFdvcmxkIQ=="

Decodifique um blob embrulhado e multi-linha com um heredoc. Colocar o delimitador entre aspas simples impede que o shell interprete qualquer coisa lá dentro:

base64 -d <<'EOF'
SGVs
bG8s
IFdv
cmxk
IQ==
EOF

Os quatro imprimem Hello, World!. No macOS e nos BSDs, troque -d por -D em todos os exemplos acima; o resto da sintaxe é idêntico.

Entrada bagunçada é a norma

O Base64 que você encontra na vida real raramente é uma linha limpa. Ele chega embrulhado a cada 76 caracteres (convenção MIME) ou 64 caracteres (convenção PEM), exportado do Windows com quebras de linha CRLF, ou copiado de uma janela de chat com espaços perdidos no meio. A boa notícia: o decodificador não se importa onde estão as quebras de linha, desde que sejam newlines de verdade.

O remédio universal para qualquer estilo de embrulho é lavar as quebras de linha antes de decodificar:

tr -d '\r\n' < blob.b64 | base64 -d

Os carriage returns são o caso especial. Um newline é entrada permitida, mas um \r não é um newline para os decodificadores rigorosos. Um blob que cruzou um sistema Windows derruba o decodificador GNU, que imprime um resultado parcial e depois falha; a correção é remover os carriage returns primeiro:

printf 'SGVs\r\nbG8s\r\n' | tr -d '\r' | base64 -d

Se você vê fragmentos como Hel seguidos de um erro, está em cima de um blob CRLF. O mesmo banho, tr -d '\r\n', antes de decodificar é o hábito portável para qualquer entrada que você não produziu.

Para entrada de verdade corrompida, o GNU e o uutils oferecem a flag -i (--ignore-garbage), que pula caracteres fora do alfabeto e decodifica o que consegue:

printf 'SGVs!bG8' | base64 -di

Isso imprime Hello. Antes de transformar o -i em hábito padrão, saiba por que o padrão avisa contra isso: o RFC 4648, seção 3.3, diz que as implementações devem rejeitar dados contendo caracteres fora do alfabeto, porque caracteres ignorados podem ser explorados como um canal encoberto que contrabandeia dados que nunca aparecem na saída decodificada. Recorra ao -i quando uma colagem de um documento trouxe pontuação junto, não quando você está verificando dados de que confia.

Aqui está como os três decodificadores principais se comportam de verdade nas bordas, com as ferramentas de 2026 (uutils 0.8.x, GNU coreutils 9.7, BusyBox 1.37):

Entrada uutils 0.8.x GNU 9.7 BusyBox 1.37
SGVsbG8sIFdvcmxkIQ==, limpo Hello, World!, exit 0 Hello, World!, exit 0 Hello, World!, exit 0
SGVsbG8s, oito caracteres, sem padding Hello,, exit 0 Hello,, exit 0 Hello,, exit 0
Zg, dois caracteres, sem padding f, exit 0 f, exit 0 erro, "truncated input"
SGV, três caracteres, sem padding erro, sem saída He impresso, depois erro erro, "truncated input"
Linhas embrulhadas em CRLF decodifica sem problemas, exit 0 saída parcial, depois erro decodifica sem problemas, exit 0
SGVs!bG8, pontuação perdida erro (com -i: Hello) saída parcial (com -i: Hello) saída parcial (sem a flag -i, de jeito nenhum)
SGV=, bits sobrando não canônicos erro, sem saída He impresso, depois erro He, exit 0
TQ==junk, lixo depois do padding exit 0, continua decodificando o junk exit 0, continua decodificando o junk M impresso, depois "truncated input" (exit 1)

Três conclusões dessa tabela. Primeira: não existe regra universal para caudas sem padding: o BusyBox quer que o comprimento seja um múltiplo de quatro, enquanto o GNU e o uutils aceitam os restos legais, mas só quando os bits sobrando são todos zero (por isso o Zg passa e o SGV não). Segunda: o GNU e o BusyBox escrevem os bytes que já decodificaram antes de falhar, então um script que redireciona para um arquivo e só depois checa o código de saída vai manter tranquilamente um arquivo decodificado pela metade. Sempre cheque o status de saída, e trate qualquer arquivo deixado por uma decodificação falha com desconfiança. Terceira, aquela última linha: o uutils e o GNU continuam decodificando o junk depois do == e terminam com exit 0, porque nada lhes diz que o stream deveria ter terminado; o BusyBox é a exceção, imprime o byte antes do padding e depois falha com um erro de entrada truncada. Se lixo no final importa para você, valide a forma da entrada antes de confiar na saída.

base64url: o alfabeto dos tokens e das URLs

A seção 5 do RFC 4648 define um segundo dialeto: a mesma matemática de 6 bits, mas com - e _ no lugar de + e /, e o padding descartado, porque uma URL raramente precisa anunciar o comprimento exato em bytes. O RFC é pontual sobre isso: essa codificação "não deve ser considerada a mesma que a codificação base64". Se você já olhou para um JWT, já encontrou o dialeto, porque seus segmentos são base64url com o padding removido.

A receita do shell é uma troca em duas etapas: traduza os caracteres URL-safe de volta para seus primos padrão, e então decodifique:

printf '%s' "_k-C" | tr '_-' '/+' | base64 -d | xxd -p

Isso devolve fe4f82, três bytes brutos que estavam de roupa de URL. A troca é posicional, então a direção importa: para codificar vai tr '+/' '-_', para decodificar vai tr '_-' '/+'. Inverter não gera erro; só produz silenciosamente bytes diferentes, que é o pior tipo de bug.

Agora a armadilha que pega quem trata base64url como Base64 comum. O padding é opcional no dialeto, e um segmento cujo comprimento é três módulo quatro é exatamente a forma que os decodificadores rigorosos examinam de perto. O movimento robusto é restaurar primeiro os caracteres de = que faltam, o que é uma função pequena:

b64url_decode () {
  local s=$1
  local n
  n=$(printf '%s' "$s" | wc -c | tr -d '[:space:]')
  while [ $(( n % 4 )) -ne 0 ]; do
    s="${s}="
    n=$(( n + 1 ))
  done
  printf '%s' "$s" | tr '_-' '/+' | base64 -d
}
b64url_decode "eyJzdWIiOiJob21lciJ9"

A última linha imprime {"sub":"homer"}, um objeto JSON sem adornos que um servidor web assinou ou lacreou um momento antes. Segmentos cujo comprimento é um módulo quatro são malformados de cara, e nenhuma quantidade de padding os salva, então a rejeição da função a esse caso é um recurso.

Existe também um caminho nativo no GNU coreutils: o basenc, o irmão mais velho do base64, entende o dialeto diretamente:

printf '%s' "Zg==" | basenc --base64url -d

Isso imprime f, o byte escondido em dois caracteres. Um aviso antes de você construir pipelines em cima: o basenc do GNU dessa época decodifica até entrada base64url sem padding (um Zg puro) sem reclamar, enquanto o basenc do uutils ainda quer o padding restaurado primeiro, como o exemplo acima mostra. A funçõezinha acima funciona em todo lugar, e é por isso que é a escolha portável.

Abrindo um JWT

Um JSON Web Token é três segmentos base64url unidos por pontos, segundo o RFC 7515. Os dois primeiros são JSON puro (o cabeçalho e os claims do payload), então decodificam direto para texto legível. O terceiro é a assinatura, um resumo binário bruto, então deixe-o em paz: decodificá-lo te dá os bytes da assinatura, não uma mensagem.

token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123"
b64url_decode "$(printf '%s' "$token" | cut -d. -f2)"

Isso imprime {"sub":"42"}, o claim do subject, sem nenhum servidor envolvido. Leia os claims com a cabeça limpa sobre o que decodificar é e o que não é: é revelar, não é verificar. A assinatura não diz nada até o servidor que guarda a chave recalculá-la, o que é um trabalho para o openssl dgst (o artigo de codificação mostra a dança completa de cunhagem), não para este. Um erro comum de campo é tratar um payload decodificado como prova de que um token é válido; um atacante pode cunhar tokens não assinados ou fracamente assinados à mão, e um decodificador vai lê-los todos com prazer.

Arquivos, bytes e a muralha das variáveis

Decodificar te entrega bytes brutos. Eles podem formar uma frase em inglês, ou podem ser o meio de um PNG, uma biblioteca compartilhada ou um arquivo zip. O arquivo é o único lugar num script de shell onde bytes estão completamente seguros, então o fluxo padrão é decodificar-para-arquivo e então comparar:

base64 -d photo.b64 > photo.png

Quando o original está pertinho de você, uma comparação byte a byte é a única prova que importa:

cmp photo.png photo.png.orig && echo "byte-for-byte identical"

Quando o original está longe, compare checksums em vez disso:

sha256sum expected.bin
base64 -d blob.b64 | sha256sum

Dois hashes batendo e sua decodificação é comprovadamente exata, o que vence qualquer quantidade de olho nu num visualizador de imagem ou num hex dump.

Variáveis de shell são outra história, e elas são uma muralha por duas razões. A substituição de comando $(...) remove todos os newlines finais da saída, e não consegue guardar byte NUL nenhum: o bash imprime um aviso e os descarta em silêncio. Um payload de três bytes de 41 00 42 mostra os dois problemas numa demo só:

printf 'QQBC' | base64 -d > out.bin
xxd out.bin

O arquivo guarda os três bytes (41 00 42). O caminho da variável não:

v=$(printf 'QQBC' | base64 -d)
printf '%s' "$v" | wc -c

Você obtém 2, com um aviso no standard error sobre o byte nulo ignorado. A lição é curta: se os dados podem ser binários, decodifique-os num arquivo, inspecione com xxd e nunca deixe passar por uma variável.

Onde o Base64 se esconde no trabalho real

Quando decodificar fica confortável, você começa a notar o formato em todo lugar. Aqui estão os cantos do trabalho real de shell onde ele aparece, cada um com o movimento exato.

Secrets do Kubernetes. Todo campo sob .data num secret é Base64, e a documentação oficial deixa claro que isso é codificação, não criptografia. Ler um de volta é um one-liner clássico:

kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo

Patches binários do Git. Quando um diff toca em arquivos binários, o git diff --binary emite um bloco GIT binary patch. Não recorra ao base64 -d aqui: as linhas daquele bloco são uma codificação estilo base85 do próprio git (cada linha começa com um caractere de comprimento, A-Z ou a-z, seguido de dados base85), não Base64, e um decodificador comum vai engasgar com elas. A ferramenta certa é a dona do formato:

git diff --binary | grep -a -A2 'GIT binary patch'

Alimente o diff com git apply ou git am, e deixe que eles façam o desempacotamento.

Data URIs. Uma imagem embutida em HTML ou CSS parece data:image/png;base64,iVBOR.... Corte tudo até a vírgula, incluindo a vírgula, lave as quebras de linha, decodifique, e você segura o arquivo:

cut -d, -f2- icon.uri | tr -d '\r\n' | base64 -d > icon.png

Armadura PEM. Certificados e chaves privadas embrulham seu Base64 em linhas de moldura que não são Base64 de jeito nenhum. Selecione o bloco armado, jogue fora as duas linhas de moldura e decodifique para o binário DER bruto:

awk '/BEGIN CERTIFICATE/{f=1;next} /END CERTIFICATE/{f=0} f' cert.pem | tr -d '\r\n' | base64 -d > cert.der

Troque CERTIFICATE por PRIVATE KEY ou pelo rótulo que seu arquivo carregar; a forma é a mesma.

E-mail MIME. Qualquer parte de um e-mail com Content-Transfer-Encoding: base64 é embrulhada a cada 76 caracteres com quebras de linha CRLF, porque é isso que o RFC 2045 prescreve. A combinação portável é o banho mais a decodificação:

tr -d '\r\n' < attachment.b64 | base64 -d > attachment.bin

Colagens da área de transferência. Texto copiado de um navegador, de uma janela de chat ou de um documento chega com espaços perdidos e um newline final. Espaços não são caracteres do alfabeto, então os decodificadores rigorosos rejeitam a colagem; o resgate padrão é removê-los primeiro:

tr -d ' \r\n' < pasted.b64 | base64 -d

Bytes primeiro: charsets e Unicode

O erro mais repetido no trabalho com Base64 é pensar em caracteres quando o formato só conhece bytes. O base64 -d te entrega bytes brutos, e se eles viram texto legível é uma decisão tomada por quem os lê em seguida. Essa decisão é um charset, e ela acontece depois da decodificação, nunca dentro dela.

O UTF-8 é a premissa padrão e normalmente a certa. A palavra café em UTF-8 tem cinco bytes, e a decodificação é um prazer:

printf 'Y2Fmw6k=' | base64 -d | xxd -p

Isso é 636166c3a9: caf mais os dois bytes UTF-8 c3 a9 do é. Sistemas mais antigos, porém, vão te entregar bytes Latin-1 (ISO-8859-1), onde a mesma letra é o byte único e9. Decodificar um blob assim e imprimi-lo direto num terminal UTF-8 te dá um caractere torto; a correção é reinterpretar os bytes com iconv antes que qualquer outra coisa os veja:

base64 -d latin1.b64 | iconv -f ISO-8859-1 -t UTF-8 > utf8.txt

Algumas verdades de nível de byte que economizam tempo real de debug:

  • Um BOM UTF-8 são os três bytes ef bb bf, que codificam para 77u/. Repare no /, um caractere hostil a URL, que é exatamente o tipo de coisa para a qual o base64url existe. Se um arquivo decodificado "tem lixo invisível na frente", cheque os três primeiros bytes com xxd.
  • Um emoji como 😀 representa quatro bytes UTF-8 e vira oito caracteres Base64, 8J+YgA==. O + ali dentro é o alfabeto padrão funcionando exatamente como projetado; na roupa base64url vira 8J-YgA.
  • Sequências UTF-8 inválidas decodificam perfeitamente bem como bytes e depois aparecem como lixo ou caracteres de substituição. Isso não é uma falha do Base64; a decodificação fez o seu trabalho. O xxd ou hexdump -C mostra o que os bytes realmente são.
  • A locale do seu terminal decide como ele renderiza aqueles bytes. "O terminal mostra lixo" é uma afirmação sobre exibição, não sobre dados. Os bytes não mudaram na viagem.

Blobs grandes: streams, divisões e velocidade

O Base64 é um formato de stream, e o decodificador funciona como um pipe de streaming de verdade: um arquivo de 10 GB nunca fica na memória; os bytes só passam por ele. Isso deixa o lado da decodificação quase entediante em escala, que é exatamente o que você quer.

Quando um blob grande foi dividido em chunks para transferência (um limite de e-mail, um tamanho de anexo de chamado, uma mensagem de IM), remontar é só um cat na ordem certa, seguido do banho de sempre:

cat part_* | tr -d '\r\n' | base64 -d > big.bin

Guarde um modelo mental para tamanhos: a forma codificada é sempre cerca de um terço maior que o original, quatro caracteres por três bytes. Então quando alguém te diz que o arquivo .b64 deveria ter o mesmo tamanho da coisa que esconde, está errado, e agora você pode dizer a quantas: um payload de 300 MB chega como uns 400 MB de texto.

Velocidade não é uma preocupação na prática. São loops dirigidos por tabela sobre memória comum, e numa máquina moderna um blob de 200 MB decodifica em mais ou menos um décimo de segundo com GNU, e até a mais lenta das implementações comuns (BusyBox) é só algumas vezes mais lenta que isso, ainda bem abaixo de dois segundos. O uso de memória fica plano não importa quão grande a entrada fique, porque nada é bufferizado.

Quando a máquina não tem base64

A maioria dos sistemas tem pelo menos uma das ferramentas acima, e a maioria tem várias. Se a sua plataforma não tem coreutils nenhum (um container esguio, um appliance incomum), os caminhos de instalação ficam assim:

Plataforma Como obter Notas
Debian / Ubuntu preinstalado; apt install coreutils se estiver esguio As versões atuais do Ubuntu padrãoizam a família uutils (desde a 25.10), com o par GNU acessível como gnubase64; o Debian 13 ainda empacota GNU coreutils por padrão
RHEL / Fedora dnf install coreutils preinstalado em praticamente toda imagem
Alpine apk add busybox (normalmente já está lá) busybox base64 -d; sem a flag -i
macOS embutido; brew install coreutils para a versão GNU flags BSD: -D para decodificar, -b para largura de linha; o brew te dá o gbase64

E se nenhum gerenciador de pacotes for uma opção, os fallbacks universais abaixo todos leem do standard input e escrevem bytes no standard output, então entram nos mesmos pipelines:

openssl base64 -d -A -a < in.b64 > out.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out.bin
python3 -c 'import base64, sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.buffer.read()))' < in.b64 > out.bin

Num sistema BusyBox com o applet base64 compilado fora, o guarda antigo ainda funciona: o uuencode -m produz Base64 MIME, e seu irmão lê de volta:

busybox uudecode -o out.bin in.uu

Armadilhas que comem tardes inteiras

Tudo abaixo é comportamento que você vai encontrar no campo, reunido num só lugar:

Armadilha O que acontece A correção
openssl base64 -d sozinho Não faz nada em silêncio e sai com exit 0, porque -d significa descriptografar lá openssl base64 -d -A -a, e trate um resultado de zero bytes como erro
Decodificar no macOS com -d O base64 do BSD rejeita a flag (ou a trata como debug) -D, ou instale o coreutils para ter o gbase64
Quebras de linha CRLF na entrada O GNU imprime um resultado parcial e depois falha; o uutils e o BusyBox aceitam tr -d '\r\n' primeiro, sempre para entrada estrangeira
Caudas sem padding BusyBox rejeita qualquer resto; GNU e uutils aceitam apenas caudas canônicas restaurar o padding de = que falta antes de decodificar
Lixo depois do padding, ex.: TQ==junk uutils e GNU continuam decodificando e saem com exit 0; o BusyBox dá erro validar a forma da entrada antes de confiar na saída
Bits sobrando não canônicos, ex.: SGV= uutils e GNU rejeitam (o GNU depois de saída parcial); o BusyBox decodifica mesmo assim a entrada está corrompida; gere novamente a codificação a montante
Ler a saída decodificada numa variável $(...) remove newlines finais e não consegue guardar byte NUL nenhum decodificar num arquivo, inspecionar com xxd
-i em entrada não confiável corrupção vira um sucesso em silêncio; caracteres ignorados podem carregar dados ocultos decodificar em modo estrito, ler o erro, corrigir a origem
Misturar os dois alfabetos decodificar base64url como padrão (ou o contrário) produz bytes errados ou um erro conhecer o formato antes de decodificar; a troca é tr '_-' '/+'
Tratar um secret decodificado como secret um comando desfaz; o RFC registra incidentes reais de credenciais vazadas criptografia de verdade, não codificação

A linha do OpenSSL merece um parágrafo só dela, porque a falha é tão silenciosa. No OpenSSL 3.x, o -d do app standalone é a opção geral de "descriptografar" dele, e o processamento Base64 é um modo separado selecionado com -a. Então o openssl base64 -d lê a sua entrada, não faz nada, não imprime nada, e sai com exit 0. A decodificação que funciona é openssl base64 -d -A -a, onde -A diz que a entrada é uma linha contínua. Se você tiver que usar OpenSSL para decodificar, trate uma saída de zero bytes como erro, toda santa vez.

Uma rotina apertada

Os hábitos que impedem o Base64 de ganhar de você em algum momento:

  • Falhar barulhento. Rode scripts com set -euo pipefail e cheque códigos de saída. Todos os decodificadores comuns saem com exit 1 em entrada ruim; o OpenSSL é o barulhento que ficou quieto, então para ele, cheque também que a saída não está vazia.
  • Provar a ida e volta. cmp ou sha256sum entre os bytes esperados e os restaurados é a única prova que importa. Nunca confie nos olhos com binário.
  • Binário para arquivos, nunca para variáveis. Newlines finais e bytes NUL são ambos vítimas da substituição de comando.
  • Lavar entrada estrangeira. tr -d ' \r\n' antes de decodificar qualquer coisa que cruzou uma fronteira de plataforma.
  • Ficar estrito por padrão. Mantenha -i desligado até ver exatamente o que estava errado; uma falha estrita te diz o local, uma tolerante não te diz nada.
  • Nomear o alfabeto. Base64 padrão e base64url são codificações diferentes segundo o RFC 4648. Decodifique um JWT como base64url, um anexo de e-mail como padrão, e nunca troque caracteres sem saber por quê.
  • Nunca imprimir o que você acabou de decodificar. O ponto inteiro do formato no mundo de segredos é invisibilidade; o ponto inteiro de um log é visibilidade. Esses dois objetivos não se misturam.

Um shim pequeno e portável para a pergunta "qual flag esta máquina quer":

case "$(base64 --version 2>&1 | head -2)" in
  *uutils*|*GNU*|*BusyBox*) b64d () { base64 -d; } ;;
  *)                        b64d () { base64 -D; } ;;
esac
b64d < in.b64 > out.bin

O case pega as famílias GNU, uutils e BusyBox pelo banner de versão - as três aceitam a flag minúscula - e recorre à flag BSD em qualquer outra coisa, que é exatamente a divisão em quatro do mundo real: GNU, uutils, BusyBox, BSD.

Como o shell aprendeu a decodificar

O formato é antigo; o comando que você está digitando não é. Uma linha do tempo curta do lado shell da história:

  • 1980, Berkeley. Mary Ann Horton escreve uuencode e uudecode na Universidade da Califórnia, Berkeley, para carregar arquivos binários (normalmente comprimidos) através do e-mail. O nome significa "codificação Unix-para-Unix", uma codificação segura para mover arquivos entre sistemas Unix que talvez não compartilhem um charset. Por décadas, este, e não o Base64, é para o que os usuários de shell recorrem.
  • A era do dial-up. As primeiras codificações base andam sobre o mesmo problema: uuencode no UNIX, BinHex no TRS-80 e no Apple II com o Macintosh um passo atrás, cada uma assumindo apenas os caracteres que seu próprio terminal conseguia imprimir.
  • 1993. O MIME padroniza o Base64 para e-mail (RFC 1521, depois RFC 2045), com o embrulho de linha a cada 76 caracteres que ainda define o padrão do base64 hoje.
  • 2003 e outubro de 2006. O RFC 3548 arruma a família, e o RFC 4648 o substitui com os alfabetos e a regra de decodificação estrita que este artigo continua citando, base64url incluído.
  • 15 de agosto de 2006. O coreutils 6.0 adiciona o próprio comando base64, seu arquivo NEWS o creditando sem rodeios como "funcionalidade de codificação e decodificação base64 (RFC 3548)". Antes dessa data, usuários de shell no Linux recorriam a openssl base64, uuencode -m, Perl ou Python, e é por isso que tantos scripts antigos assumem que o OpenSSL é a única opção da cidade.
  • OS X 10.7. O macOS empacota seu próprio base64, o sabor BSD com a flag -D e sem embrulho de linha padrão, que é de onde vem a divisão entre -d e -D.
  • Março de 2024. O coreutils 9.5 relaxa o decodificador: padding não é mais obrigatório ao decodificar, e codificações com bits sobrando não zero agora são diagnosticadas como corrupção em vez de aceitas em silêncio.
  • 2025. A reescrita do coreutils em Rust (uutils) vira o padrão nas versões atuais do Ubuntu. Mesmo nome de comando, mesmas flags, um motor novo com suas próprias opiniões de borda, como aceitar -D como alias.

Curiosidades que valem a pena guardar

  • O comando é mais jovem que o formato. O Base64 está no e-mail desde 1993, mas o comando base64 só apareceu em 2006. Treze anos de scripts de shell fizeram esse trabalho com outras ferramentas, e você ainda encontra as digitais delas em todo lugar.
  • Um fóssil no texto de ajuda. O base64 do uutils ainda descreve seu alfabeto como "RFC 3548" na ajuda, o antecessor aposentado do RFC 4648, enquanto seu irmão GNU já cita o padrão atual. Um fóssil miúdo, visível só se você ler a ajuda.
  • O no-op silencioso mais caro da caixa de ferramentas. O openssl base64 -d não faz nada e sai com exit 0. Uma hora inteira de debug, fora, e o teste do código de saída passou.
  • O BusyBox guarda os bits sobrando. Ele decodifica SGV= com prazer, cujos bits sobrando são não zero e portanto não canônicos, enquanto seu primo GNU faz escândalo com a mesma entrada. Mesmo RFC, nervos diferentes.
  • O nome do formato é verdadeiro em toda máquina. O printf 'base64' | base64YmFzZTY0 no GNU, uutils, BusyBox e OpenSSL, todos iguais. É verdade desde 2006 e sempre será.
  • Base85 não é Base64. Os blocos de "binary patch" do Git parecem Base64 para o olho inexperiente, mas as linhas com comprimento à frente são um dialeto estilo base85 de um tipo só. O impostor que custa às pessoas um desvio de grep-e-decodificar.
  • Onze caracteres, sessenta e quatro bits. Um ID de vídeo do YouTube é uma string base64url de 11 caracteres, um número de 64 bits vestindo roupa de URL, e é por isso que ele pode aparecer numa URL sem um único sinal de percent.
  • Os decodificadores discordam de um caractere. Uma única string como SGV= divide o campo em três acampamentos, como a tabela de comportamento acima mostra. Se uma decodificação falha em entrada "obviamente válida", você provavelmente está em cima da linha dos bits sobrando, e a codificação nunca foi canônica para começar.

Então, da próxima vez que uma string de letras, dígitos, mais e barra cair no seu terminal, você conhece a história toda: qual decodificador está de olho, qual alfabeto está falando, onde os newlines estão escondidos, e exatamente como trazer os bytes de volta, intactos e provados byte a byte, sem perder uma hora para um carriage return. E quando o trabalho aponta para o outro lado, empacotando o seu próprio binário num envelope de texto para a viagem, o artigo relacionado de codificação Base64 vinculado abaixo cobre esse ritual na mesma profundidade.

Última atualização: 2026-09-08

Artigo relacionado: Codificação Base64 em Bash: um guia completo