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.

Codificação Base64 em Go: um guia completo

De tempos em tempos seu programa Go tem que entregar dados binários para um mundo que só aceita texto: um campo JSON que precisa continuar string, uma URL que precisa continuar um único token, um anexo de e-mail cruzando servidores que se lembram dos dias de 7 bits, uma imagem que quer morar dentro do HTML para a página pular uma requisição. Base64 é o mensageiro exatamente para esse trabalho, e a página inicial deste site já explica o formato em profundidade, então este artigo vai direto para a arte de empacotar: produzir strings base64 em Go que todo decodificador do planeta consegue abrir sem briga.

A boa notícia logo de cara: o codificador é a metade tranquila da história. Um método, sem retorno de erro, sem modo de falha, saída idêntica byte a byte em todo lançamento do Go desde o primeiro estável. Todo o drama mora em volta desse método: escolher o alfabeto certo para o canal por onde a string vai viajar, a chamada Close que engole em silêncio seus últimos dois bytes quando você esquece dela, o imposto de tamanho que o formato carrega, e o fato de que Go, como Python, Java e Node, nunca quebra a saída em 76 caracteres. Conheça a função primeiro, depois conheça as armadilhas.

Empacotando sem falhar

Noventa por cento da vida de codificação em Go é um método do tipo Encoding, e como todo ponto de entrada de codificação do pacote (Encode, AppendEncode), ele não tem retorno de erro:

func (enc *Encoding) EncodeToString(src []byte) string

Entregue bytes, ele devolve uma string, e esse é o contrato inteiro:

package main

import (
  "encoding/base64"
  "fmt"
)

func main() {
  packed := base64.StdEncoding.EncodeToString([]byte("Man"))
  fmt.Println(packed) // TWFu
}

Não há valor de erro porque não tem nada para dar errado: qualquer byte é entrada legal, o alfabeto sempre cobre, e a saída é sempre ASCII puro. Três propriedades valem a pena memorizar, porque respondem a metade de todas as perguntas futuras. Primeiro, o comprimento da saída é uma função aritmética pura do comprimento da entrada, e o pacote até te entrega a fórmula como método: EncodedLen(n) devolve (n+2)/3*4 para codificações com padding, então 3 bytes de entrada viram 4 caracteres, 6 viram 8, e assim por diante. Segundo, o formato carrega um imposto de tamanho: a cada três bytes de dados voltam quatro caracteres, que é a expansão familiar de mais ou menos 33 por cento que aparece nas suas contas de banda e nas cotas de armazenamento. Terceiro, o método é determinístico: os mesmos bytes sempre produzem a mesma string, em qualquer máquina, em qualquer versão do Go, para sempre. Esse determinismo é o que faz do base64 um formato de serialização em vez de um mistério.

Uma nota específica do Go no lado da entrada: o método recebe []byte, não string, e a conversão []byte(...) é explícita em cada ponto de chamada - o Go nunca converte uma string em slice por você - e ela produz uma cópia independente dos bytes da string. O compilador pode eliminar essa cópia quando o slice só é lido e não escapa, por isso o custo geralmente é imensurável; mas se o slice é armazenado ou devolvido, o runtime paga uma cópia O(n) de verdade. Texto num programa Go é UTF-8 por convenção, então quando você codifica uma string está codificando os bytes UTF-8 dela, e é exatamente isso que todo decodificador moderno do outro lado espera. Mais sobre isso na seção Texto, bytes e Unicode.

Como o Go entrega

Como tudo neste artigo, o codificador vem do pacote da biblioteca padrão encoding/base64, que embarca desde o primeiro lançamento da linguagem e cujo arquivo de origem ainda carrega o cabeçalho de copyright de 2009. Não há módulo para baixar, flag de recurso para virar ou peculiaridade de plataforma: se go version funciona, go doc encoding/base64 imprime a API inteira para você.

Até o momento desta escrita, o lançamento mais novo é o Go 1.27.1, lançado em 1 de setembro de 2026, com a linha Go 1.26 (atualmente 1.26.8) como a outra trilha suportada. Instale o Go nos tarballs oficiais do go.dev/dl, no gerenciador de pacotes da sua distribuição (sudo apt install golang-go), ou via o wrapper golang.org/dl se você equilibra versões. A API de base64 é idêntica nas duas linhas suportadas, e a tabela abaixo é a história inteira do que alguma vez mudou, o que é uma lista curta para um pacote tão central:

Lançamento Ano O que mudou no encoding/base64
Go 1.0 2012 Pacote estável desde o primeiro dia; copyright da origem 2009
Go 1.5 2015 RawStdEncoding e RawURLEncoding adicionados para saída sem padding
Go 1.8 2017 Strict() adicionado para decodificação canônica (lado do decodificador)
Go 1.22 2024 AppendEncode e AppendDecode adicionados; WithPadding agora rejeita argumentos ruins
Go 1.27.1 2026 Lançamento atual; API inalterada, comportamento estável byte a byte pela promessa do Go 1

A consequência prática dessa história: código escrito contra essa API em 2015 compila e se comporta de forma idêntica hoje, e as strings que seu programa codifica em 2026 vão decodificar corretamente em qualquer lançamento do Go, passado ou futuro. Para um formato de serialização, esse é o superpoder silencioso.

Escolhendo o alfabeto para o destino

Codificar tem uma decisão de verdade, e é uma questão de viagem: para onde vai essa string? O Go te dá quatro codificadores prontos, e cada um é ajustado para um canal diferente:

Codificador Alfabeto Padding Mande para lá quando a string viajar por
StdEncoding A-Z a-z 0-9 + / = corpos JSON, partes MIME de e-mail, data URLs, HTTP Basic auth, PEM, a maioria das APIs
URLEncoding A-Z a-z 0-9 - _ = caminhos e queries de URL, nomes de arquivo, qualquer lugar onde + ou / precisaria de escape
RawStdEncoding A-Z a-z 0-9 + / nenhum strings compactas de alfabeto padrão onde o padding não pode aparecer
RawURLEncoding A-Z a-z 0-9 - _ nenhum segmentos de JWT, identificadores compactos, tokens embutidos em URLs

O raciocínio por trás das variantes é o raciocínio por trás do formato em si. O alfabeto padrão é o que o MIME e a maioria das APIs esperam, então é o padrão e a resposta segura quando ninguém te disse o contrário. O alfabeto URL-safe existe porque + e / são caracteres reservados em URLs: um mais numa query string é frequentemente lido como espaço, e uma barra começa um novo segmento de caminho, então base64 padrão numa URL ou quebra ou precisa de percent-escape nos caracteres que carregam +, / ou = - alguns por cento de um token típico. Trocá-los por - e _, que são legais sem escape em caminhos, queries e nomes de arquivo, é a correção que o RFC 4648 padronizou. As variantes Raw dispensam os sinais de igual do final por completo, o que importa em contextos onde o padding é proibido ou simplesmente nunca usado, como segmentos de JWT. A regra que te salva da maioria do debugging: o codificador que você escolhe e o decodificador que o outro lado usa formam um contrato, e o contrato é escrito pelo destino, e não por você.

Se um sistema com o qual você está conversando definiu um alfabeto privado de 64 caracteres, base64.NewEncoding("...64 chars...") monta um codificador para ele, e WithPadding(rune) deixa você trocar o caractere de padding ou desativá-lo com NoPadding. As duas funções entram em pânico com argumentos inválidos (comprimento errado do alfabeto, caractere duplicado, quebra de linha no alfabeto, caractere de padding que conflita com o alfabeto), então monte seus codificadores personalizados uma vez, na inicialização, nunca num caminho quente.

A armadilha do Close

Aqui está a armadilha mais famosa deste pacote, e ela só aparece quando você codifica um stream em vez de uma string. NewEncoder envolve qualquer io.Writer num escritor que codifica base64, e como o base64 funciona em blocos de três bytes de entrada produzindo quatro caracteres de saída, o codificador tem que guardar seus últimos um ou dois bytes em buffer, esperando para ver se vêm mais. Eles só saem quando você fecha:

package main

import (
  "bytes"
  "encoding/base64"
  "fmt"
)

func main() {
  var buf bytes.Buffer
  enc := base64.NewEncoder(base64.StdEncoding, &buf)
  enc.Write([]byte("hello"))
  fmt.Println(buf.String()) // aGVs  -- onde está o "lo"?

  buf.Reset()
  enc = base64.NewEncoder(base64.StdEncoding, &buf)
  enc.Write([]byte("hello"))
  enc.Close()
  fmt.Println(buf.String()) // aGVsbG8=  -- a codificação completa de "hello"
}

O primeiro print é a lição inteira: sem Close, o codificador emitiu só o primeiro bloco completo, três bytes de "hello" virando "aGVs", e os dois bytes restantes simplesmente sumiram para o buffer interno. O segundo print, depois do Close, é a string correta e completa. A correção é um hábito, não uma técnica: no momento em que você cria um codificador, crie a limpeza dele também:

enc := base64.NewEncoder(base64.StdEncoding, w)
defer enc.Close() // lembre de checar o erro devolvido em código de produção

Dois detalhes deixam essa armadilha mais afiada do que parece. Primeiro, Close faz trabalho de verdade: flusha o bloco parcial pendente e pode falhar, porque escreve no escritor subjacente, então a versão idiomática checa o erro dele, especialmente quando o destino é uma rede ou um disco. Segundo, a documentação diz que é erro chamar Write depois do Close, mas o runtime não aplica essa frase. Se você escrever de novo depois de fechar, o codificador começa em silêncio um bloco novo e o anexa, produzindo uma string com padding no meio, que é base64 inválido que a maioria dos decodificadores rejeita com um offset confuso. O contrato é seu para manter.

Quebra de linha, do jeito Go

Toda outra implementação grande de base64 que você já usou quebra a saída: o MIME quer linhas de no máximo 76 caracteres, o PEM usa 64, clientes de e-mail pelo mundo inteiro enfiam um CRLF de tempos em tempos. O codificador do Go não faz nada disso. Ele emite uma linha contínua, não importa o tamanho do payload, e faz isso desde que o pacote nasceu. A saída para um megabyte de dados é uma única linha de um megabyte e um terço, do começo ao fim, sem quebras.

Essa é uma escolha deliberada, não um esquecimento. O formato funciona de forma idêntica com ou sem quebras de linha, o próprio decodificador do Go as pula em qualquer lugar da entrada, e um codificador que enfiasse CRLFs em silêncio nos seus dados surpreenderia programas que guardam a string numa coluna de banco de dados ou a comparam por igualdade. O custo é que você tem que quebrar por conta própria quando o canal exige, e o jeito é um helper pequeno:

package main

import (
  "bytes"
  "encoding/base64"
  "fmt"
)

func wrapAt(s string, width int) string {
  var out bytes.Buffer
  for i := 0; i < len(s); {
    end := i + width
    if end > len(s) {
      end = len(s)
    }
    out.WriteString(s[i:end])
    out.WriteByte('\n')
    i = end
  }
  return out.String()
}

func main() {
  raw := base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{0x42}, 100))
  fmt.Print(wrapAt(raw, 76))
}

Uma nota sobre a direção da viagem: como o decodificador do Go ignora quebras de linha em qualquer lugar, entrada quebrada decodifica perfeitamente do lado Go de qualquer ponte. A outra direção é onde o cuidado é preciso: se você envia saída quebrada para um consumidor que não espera quebras (um campo JSON, uma URL, um token), remova-as primeiro, porque esse consumidor pode tratar uma quebra de linha como caractere corrompido. Saiba em qual convenção o seu canal vive, e emita de propósito.

Empacotando para e-mail e MIME

E-mail é a casa mais antiga do base64. O protocolo SMTP original foi desenhado para transportar ASCII de 7 bits, então anexos eram codificados em base64 antes do envio e decodificados na chegada, e o padrão MIME (RFC 2045) formalizou a prática: o cabeçalho Content-Transfer-Encoding: base64 marca uma parte, e o corpo deve ser quebrado em linhas de no máximo 76 caracteres com CRLF entre elas.

O pacote net/smtp do Go envia os bytes que você dá a ele, e não monta partes MIME por você, então num programa que compõe e-mails a parte base64 fica assim:

package main

import (
  "bytes"
  "encoding/base64"
  "fmt"
)

func main() {
  body := []byte("hi from Go")
  var part bytes.Buffer
  part.WriteString("Content-Transfer-Encoding: base64\r\n")
  part.WriteString("Content-Type: text/plain; charset=utf-8\r\n\r\n")

  encoded := base64.StdEncoding.EncodeToString(body)
  for i := 0; i < len(encoded); i += 76 {
    end := i + 76
    if end > len(encoded) {
      end = len(encoded)
    }
    part.WriteString(encoded[i:end] + "\r\n")
  }
  fmt.Print(part.String())
}

Três coisas para notar. O codificador padrão é o certo aqui, porque o MIME é o contexto original de alfabeto padrão. As quebras de linha são CRLF, e não a quebra nativa da plataforma, porque é isso que o RFC especifica e o que os parsers de e-mail esperam. E se seu programa envia e-mail de verdade em volume, uma biblioteca MIME mantida monta a mensagem inteira por você; o ponto deste exemplo é a metade base64, que é a parte que pertence a este pacote. Acerte o alfabeto e a convenção de linha, e o resto do MIME é problema de outra pessoa.

Empacotando arquivos

Para arquivos que cabem na memória, o padrão são as mesmas duas linhas de qualquer lugar: ler e depois EncodeToString. Para arquivos que não cabem, o streaming mantém sua memória plana, e a receita é um arquivo, um codificador, uma cópia e dois closes na ordem certa:

in, err := os.Open("photo.jpg")
if err != nil {
  panic(err)
}
defer in.Close()

out, err := os.Create("photo.b64")
if err != nil {
  panic(err)
}
enc := base64.NewEncoder(base64.StdEncoding, out)
if _, err := io.Copy(enc, in); err != nil {
  panic(err)
}
if err := enc.Close(); err != nil {
  panic(err) // flusha o bloco parcial final
}
if err := out.Close(); err != nil {
  panic(err)
}

A ordem dos closes é a parte sutil, e é a versão de arquivo da armadilha do Close: o codificador tem que fechar antes do arquivo, porque é o enc.Close que escreve o bloco parcial final no arquivo, e fechar o arquivo primeiro deixaria aquele bloco num buffer que escreve em nada. Com defer, lembre que chamadas adiadas rodam em ordem reversa, então registrar out.Close primeiro e enc.Close segundo (ou, como no exemplo acima, fechar o codificador explicitamente antes de adiar o arquivo) é o que torna a sequência segura.

Mantenha o imposto de tamanho na cabeça quando planejar em volta deste padrão: uma foto de 10 megabytes vira mais ou menos 13.3 megabytes de texto, e um arquivo de 100 megabytes vira uma string de 133 megabytes no disco. Se o destino tem cota, limite ou preço por byte, é a versão base64 do seu arquivo que está sendo contada, e não o original.

Empacotando para a web: data URLs

Navegadores carregam de bom grado uma imagem ou uma fonte de uma string que mora dentro do próprio HTML ou CSS, e essa string é uma data URL: o media type, a flag ;base64, uma vírgula e o payload, tudo numa URL só. O Go não tem helper de data URL, mas montar um é concatenação de strings, porque o formato é um contrato que dá para ver escrito:

package main

import (
  "fmt"
  "os"
  "encoding/base64"
)

func main() {
  img, err := os.ReadFile("logo.png")
  if err != nil {
    panic(err)
  }
  url := "data:image/png;base64," + base64.StdEncoding.EncodeToString(img)
  fmt.Println(url)
  // data:image/png;base64,iVBORw0KGgo...
}

Duas regras mantêm as data URLs fora da sarjeta. Inclua sempre o media type: ele é opcional na gramática (o padrão é text/plain;charset=US-ASCII), mas um navegador adivinhando o tipo do seu payload binário não é um cenário que você quer. E trate data URLs como um truque de ativo pequeno. O RFC diz que o scheme só é útil para valores curtos, e a expansão de 33 por cento é o que faz a diferença entre um ícone de 2 quilobytes que salva uma requisição e uma foto de 5 megabytes que estufa todo carregamento de página, sem cache para compartilhá-la e sem URL para entregar a ninguém. Ícones, favicons, sprites pequenos: sim. Fotografia de produto: não.

Empacotando para HTTP

Três contextos HTTP dominam o base64 em serviços Go, e dois deles vêm com ajuda embutida. O primeiro é o corpo JSON, o cavalo de batalha: você codifica um valor antes de marshalar, e o campo carrega uma string pura pela rede:

package main

import (
  "encoding/base64"
  "encoding/json"
  "fmt"
)

type avatar struct {
  Data string `json:"data"`
}

func main() {
  png := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
  a := avatar{Data: base64.StdEncoding.EncodeToString(png)}
  body, err := json.Marshal(a)
  if err != nil {
    panic(err)
  }
  fmt.Println(string(body))
  // {"data":"iVBORw0KGgo="}
}

Se um tipo aparece em muitos lugares, a jogada limpa em Go é implementar MarshalJSON e UnmarshalJSON nele, para a etapa base64 ser invisível para todo ponto de chamada. O segundo contexto é a autenticação HTTP Basic, onde a biblioteca padrão faz o trabalho inteiro: Request.SetBasicAuth(user, pass) monta o cabeçalho Authorization por você, rodando o codificador padrão no par user:pass especificado pelo RFC 2617. A única regra ali é não improvisar: Basic auth é base64 padrão com um prefixo Basic , e um alfabeto URL-safe ou um sinal de padding faltando transformam um login funcionando num 401 que ninguém consegue explicar.

O terceiro contexto são URLs, onde a string é o payload de um segmento de caminho ou de um parâmetro de query. Aqui o alfabeto padrão é uma escolha ruim, porque +, / e = colidem todos com a gramática de URL, e cada ocorrência deles precisa de percent-escape. Codifique com a variante URL-safe em vez disso, e o token sobrevive à URL intacto. Se o consumidor ainda assim percent-escapeá-lo, nada quebra, mas se não fizer, você se livrou de uma classe de 404.

Saída URL-safe

Base64 URL-safe merece a própria seção em Go porque é a variante para a qual você vai recorrer mais do que para a padrão, e porque o Go torna a troca de graça. O alfabeto alternativo do RFC 4648 substitui + por - e / por _, então a saída não precisa de escape em caminhos de URL, queries ou nomes de arquivo, e lê-se como um único token limpo numa linha de log. Os dois codificadores prontos são URLEncoding (com padding) e RawURLEncoding (sem padding):

raw := []byte{0xfb, 0x0f, 0x67, 0x01}
fmt.Println(base64.StdEncoding.EncodeToString(raw))     // +w9nAQ==
fmt.Println(base64.URLEncoding.EncodeToString(raw))     // -w9nAQ==
fmt.Println(base64.RawURLEncoding.EncodeToString(raw))  // -w9nAQ

Essa entrada única, três saídas: a versão padrão precisa de percent-escape para o seu sinal de mais, a versão URL-safe é um único token, e a versão raw dispensa o padding também. Os trabalhos Go típicos para cada uma: identificadores opacos que um serviço gera e depois guarda em URLs, rotas ou nomes de arquivo; tokens de API que clientes colam em query strings; qualquer coisa que vai aparecer numa linha de log onde um + ou uma / está a um caractere de ser confundido com sintaxe.

A disciplina que mantém isso limpo é a mesma de todo lugar neste artigo: a variante é um contrato com o consumidor. Se o outro lado espera base64 padrão e você envia URL-safe, o decodificador dele falha no primeiro hífen, e o erro vai ser um offset de byte perto do final de uma string perfeitamente boa, o que não é uma coisa óbvia de debugar. Na dúvida, pergunte o que o outro lado espera, leia a spec que ele aponta e escolha o codificador pelo destino, e não pelo hábito.

Empacotando JWTs

JSON Web Tokens são o consumidor mais visível de base64 em APIs modernas, e eles fixam a variante exata: serialização compacta JWS, por RFC 7515, é três segmentos base64url sem padding, juntados por pontos. Header, payload, assinatura. Isso significa que o codificador da vez para qualquer coisa que você monte à mão é RawURLEncoding:

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/base64"
  "encoding/json"
  "fmt"
)

func main() {
  secret := []byte("hmac-secret")
  header, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
  payload, _ := json.Marshal(map[string]any{"sub": "1234567890"})

  signingInput := base64.RawURLEncoding.EncodeToString(header) + "." +
    base64.RawURLEncoding.EncodeToString(payload)

  mac := hmac.New(sha256.New, secret)
  mac.Write([]byte(signingInput))
  signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
  fmt.Println(signingInput + "." + signature)
}

Leia aquele exemplo como uma lição sobre o que o formato é, e não como uma recomendação para entregá-lo: ele mostra exatamente onde o base64 fica (duas vezes antes de assinar, uma depois) e por que a assinatura cobre os segmentos codificados, e não o JSON cru. Em produção, assine e verifique com uma biblioteca mantida, porque JWT tem uma cauda longa de erros (drift de relógio na expiração, confusão de algoritmo, verificação de audience ausente) que a camada base64 não consegue ver. A biblioteca Go de fato é github.com/golang-jwt/jwt/v5, instalada com go get github.com/golang-jwt/jwt/v5:

package main

import (
  "fmt"
  "log"
  "time"

  "github.com/golang-jwt/jwt/v5"
)

func main() {
  secret := []byte("hmac-secret")
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "sub": "1234567890",
    "exp": time.Now().Add(time.Hour).Unix(),
  })
  signed, err := token.SignedString(secret)
  if err != nil {
    log.Fatal("signing failed:", err)
  }
  fmt.Println(signed)
}

A biblioteca executa a codificação base64url de cada segmento internamente, então você nunca toca encoding/base64 de jeito nenhum, o que é o melhor resultado: um lugar a menos para um erro de padding ou de alfabeto se esconder. E note a guarda que ela te dá de graça: o v5 rejeita tokens que afirmam alg=none a menos que você opte explicitamente com a constante UnsafeAllowNoneSignatureType dela, que é a proteção que você quer sem pensar nela.

Texto, bytes e Unicode

A posição do Go nessa questão é a mais curta de qualquer linguagem grande, e é por isso que o base64 é tão agradável aqui: uma string em Go é uma sequência de bytes somente leitura, e o texto no seu programa é UTF-8. Não há camada oculta de codificação, nenhuma surpresa de "a string na verdade é UTF-16", e nenhum flag de charset para configurar. Quando você escreve EncodeToString([]byte(myText)), está codificando os bytes UTF-8 do texto, ponto final:

s := "Café ☕"
packed := base64.StdEncoding.EncodeToString([]byte(s))
fmt.Println(packed) // Q2Fmw6kg4piV

Essa linha é a história inteira para texto moderno, incluindo emoji e CJK: base64 opera em bytes, UTF-8 é só uma sequência de bytes, e todo decodificador do outro lado que siga a mesma convenção vai te dar a mesma string de volta. A conversão []byte(...) é uma cópia independente, que o compilador elimina quando o slice só é lido e não escapa - então na prática ela não custa nada que você consiga medir.

O único caso em que a história fica mais longa é dado legado: bytes que foram produzidos por um sistema Windows-1252, Shift JIS ou ISO-8859-1 e não são UTF-8 válido. Se você codificar em base64 esses bytes como estão, transportou fielmente texto quebrado, o que é o que ninguém queria. A correção é normalizar antes de codificar, usando golang.org/x/text, para a string base64 carregar UTF-8 limpo desde o momento em que sai do seu programa:

import (
  "golang.org/x/text/encoding/charmap"
  "golang.org/x/text/transform"
)

legacy := []byte{0x43, 0x61, 0x66, 0xE9} // "Café" em Windows-1252
utf8, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), legacy)
if err != nil {
  panic(err)
}
packed := base64.StdEncoding.EncodeToString(utf8)
// Q2Fmw6k=  -- o mesmo "Café", agora bytes UTF-8 limpos prontos para viajar

O mesmo módulo cobre japanese, korean, simplifiedchinese e traditionalchinese além do charmap. A regra prática: converta uma vez, na fronteira onde bytes legados entram no seu programa, e a partir de então tudo que você codifica é UTF-8. Não converta duas vezes, não chute, e nunca deixe um payload não-UTF-8 se esgueirar para uma string base64 que um consumidor moderno vai decodificar e exibir.

Medindo o codificador

O codificador é um lookup em tabela sem ramo na entrada e sem alocação além da string de saída, e aparece nos números. Numa CPU desktop recente rodando Go 1.26, codificar 500 bytes leva mais ou menos três décimos de microsegundo com duas alocações, o que dá na ordem de um gigabyte e meio por segundo. Um megabyte de dados codifica em muito menos de um milissegundo; o codificador raramente vai ser algo que você sinta.

A única alavanca que vale conhecer é o perfil de alocação em loops quentes. EncodeToString aloca a string de saída em toda chamada, o que é o trade certo para o caso dos 99 por cento. Se você está codificando milhares de chunks por segundo para um buffer que cresce, AppendEncode, adicionado no Go 1.22, anexa os bytes codificados a um slice que você reutiliza e não faz alocação em estado estacionário uma vez que o buffer cresceu para o tamanho:

var out []byte
for _, chunk := range chunks {
  out = base64.StdEncoding.AppendEncode(out, chunk)
}

Use EncodeToString para casos únicos, AppendEncode para loops apertados e NewEncoder para streams e arquivos. Seja qual for a escolha, lembre que a rede ou o disco em volta do codificador quase sempre é a parte lenta, então profile o caminho inteiro antes de otimizar o alfabeto.

Considerações de segurança

A frase de segurança mais importante deste artigo: base64 não é criptografia, e "a gente dá base64 primeiro" não é uma medida de segurança. O alfabeto torna dados seguros para texto, e não secretos, e qualquer um com as ferramentas de desenvolvedor de um navegador lê seu base64 num instante. A confidencialidade vem do TLS e do controle de acesso, e o trabalho do base64 é levar bytes através de um canal só de texto sem corrompê-los. Mantenha esses dois trabalhos separados no seu design e na sua documentação, e você evita o comentário clássico de revisão de "a senha está protegida, olha, é base64".

A segunda consideração é tamanho. Porque o formato expande por um terço, todo limite no seu sistema tem uma versão base64: uma API que aceita 4 megabytes de JSON aceita mais ou menos 3 megabytes de dados originais quando o payload é um campo base64, uma URL com orçamento de comprimento fica mais curta em bytes crus quando o token é URL-safe e sem padding, e uma coluna de banco de dados dimensionada para o valor cru pode ser pequena demais para o valor codificado. Faça a aritmética com EncodedLen antes de armazenar, enviar ou limitar, e lembre que a expansão é sobre a entrada com que você começa, e não sobre a string com que você termina.

Terceiro, pense em onde a string codificada pode ser observada. Strings base64 são amigáveis para log e para tela, o que é um recurso, até que um anexo de 20 megabytes vira 26 megabytes de texto em base64 que seu log de acesso registra com devoção em toda requisição. Registre o comprimento, as primeiras dezenas de caracteres e o identificador, e não o payload, e você mantém seus logs legíveis e seu disco vivo. Por fim, em URLs, prefira a variante URL-safe para que seus tokens não gastem nenhum dos caracteres como percent-escapes, o que estufa a URL e ocasionalmente trava um gateway ou um proxy que tem uma visão estrita do que pertence a uma query string.

Fatos divertidos e peculiaridades do Go

Alguns fatos específicos deste pacote, para as vezes em que você quer estar certo numa code review:

  • EncodeToString é o cavalo de batalha, e como todo ponto de entrada de codificação do pacote (Encode, AppendEncode), não tem retorno de erro - codificar não pode falhar em Go, o que é um tipo raro e silencioso de liberdade: qualquer byte é entrada legal, e o único jeito de obter uma string ruim é escolher o alfabeto errado para o canal.
  • EncodedLen é aritmética pura, (n+2)/3*4 para codificações com padding, computada sem alocação e sem loop. Ele existe para você dimensionar buffers e cotas sem nunca codificar um byte.
  • O codificador de stream interno esconde um buffer de entrada de 3 bytes e um buffer de saída de 1024 bytes, por isso o NewEncoder escreve em chunks e por que o último bloco parcial só pode sair pelo Close. Os buffers são a razão da armadilha.
  • A documentação diz que é erro escrever depois de chamar Close, mas o runtime não aplica a frase. Um Write atrasado é aceito, anexa um bloco novo e produz uma string com padding no meio: base64 inválido, gerado educadamente, sem valor de erro à vista.
  • O codificador do Go nunca quebrou a saída em 76 caracteres - como os codificadores de Python, Java e Node, o codificador do Go produz uma linha para um megabyte de dados. Seu helper de quebra MIME é um projeto pessoal, o que também é um bom jeito de lembrar que as quebras de linha no base64 de e-mail são uma convenção MIME, e não um requisito base64.
  • Até agosto de 2026, mais de 244.000 pacotes públicos no pkg.go.dev importam encoding/base64. Seja lá o que for o seu programa Go, ele quase com certeza está fazendo base64 em algum lugar, saiba você ou não.
  • A promessa de compatibilidade do Go 1 se aplica a este pacote com força especial: a saída de um programa que codificou uma string em 2013 é idêntica byte a byte no Go 1.27 de hoje. Strings base64 são, em Go, efetivamente imortais.

Os erros que não param de reaparecer

Os erros de codificação que não param de ressuscitar em codebases Go, mais ou menos na ordem em que chegam:

  • Esquecer o Close no codificador de stream, e entregar uma string que falta o último ou dois bytes. O bug sobrevive a todo teste que usa entrada cujo comprimento é múltiplo de três, e é assim que ele chega à produção.
  • Fechar o arquivo antes do codificador, para o bloco parcial final flushar num handle de arquivo que já foi embora. A saída é truncada pela quantidade exata, e o erro só aparece em entradas de comprimento que não fecham bloco.
  • Esperar quebras de linha de 76 caracteres na saída MIME ou de e-mail e ficar confuso quando o Go te entrega uma linha longa só. A quebra é uma convenção de canal, e em Go é trabalho do seu código aplicá-la.
  • Usar o alfabeto padrão dentro de URLs, e depois gastar uma tarde caçando 404s e 400s que na verdade são problema de percent-encoding. Se a string vai morar numa URL, comece de URLEncoding ou RawURLEncoding.
  • Emitir padding onde o consumidor proíbe: segmentos de JWT, alguns formatos de token, alguns parsers estritos. As variantes raw existem exatamente por esse motivo, e a mensagem de erro do outro lado muitas vezes é um offset de byte bem no final da sua string.
  • Dar base64 num segredo e chamar de proteção. Não é. O cabeçalho, o token, o campo "criptografado": legível por qualquer um em meio segundo. Use TLS, use hash onde o protocolo quer hash, e deixe o base64 fazer o seu único trabalho honesto.
  • Esquecer os 33 por cento na hora de definir limites: tamanhos de corpo, larguras de coluna, orçamentos de URL, checagens de cota. A aritmética é uma chamada para EncodedLen, e o custo de pular é um 413 ou uma coluna truncada em produção.
  • Codificar texto que não é UTF-8, o que transporta fielmente a quebra. Normalize charsets legados com golang.org/x/text antes de codificar, para a string base64 carregar bytes limpos.
  • Escrever no codificador depois de fechá-lo, por hábito ou por um loop de retry. Nenhum erro é levantado, e a saída é silenciosamente inválida.
  • Assumir que o decodificador do outro lado é tão tolerante quanto o do Go. O Go pula quebras de linha em qualquer lugar, mas outras linguagens e parsers são mais estritos com espaço em branco e com comprimento de linha, então siga a convenção do canal em vez do humor do runtime do Go.

O outro lado

Essa é a história do lado da codificação: um método que não pode falhar, quatro codificadores casados com os canais por onde as strings vão viajar, um codificador de stream com um Close obrigatório, e um formato que expande seus dados por um terço e nunca, jamais, quebra as linhas. Escolha o alfabeto pelo destino, feche seus codificadores, faça a aritmética de tamanho antes, e o base64 em Go continua o utilitário silencioso e sem dependências que é desde 2009.

E quando o tráfego inverte, quando seu programa recebe uma dessas strings e precisa abri-la, o artigo relacionado sobre decodificação Base64 em Go cobre esse lado em detalhe: as regras de tolerância do decodificador, os offsets de erro que dizem o byte onde a entrada dá errado, o modo estrito para protocolos exigentes e as mesmas quatro codificações pela outra direção.

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

Artigo relacionado: Decodificação Base64 em Go: um guia completo