Devi lavorare con il formato Base64? Allora questo sito è perfetto per te! Usa il nostro praticissimo strumento online per codificare o decodificare i tuoi dati.

Codifica Base64 in Go: una guida completa

Ogni tanto il tuo programma Go deve consegnare dati binari a un mondo che accetta solo testo: un campo JSON che deve restare una stringa, un URL che deve restare un singolo token, un allegato email che attraversa server che ricordano i tempi del 7 bit, un'immagine che vuole vivere dentro l'HTML così la pagina salta una richiesta. Base64 è il corriere fatto apposta per quel lavoro, e la home page di questo sito spiega già il formato in profondità, quindi questo articolo va dritto all'arte dell'imballaggio: produrre stringhe base64 in Go che ogni decoder al mondo può aprire senza intoppi.

La buona notizia in anticipo: l'encoder è la metà gentile della storia. Un metodo, nessun valore di errore, nessun modo di fallire, output identico a byte su ogni release Go dalla prima stabile. Tutto il dramma vive intorno a quel metodo: scegliere l'alfabeto giusto per il canale attraverso cui la stringa viaggerà, la chiamata Close che inghiotte in silenzio i tuoi ultimi due byte quando la dimentichi, la tassa sulle dimensioni che il formato porta con sé, e il fatto che Go, come Python, Java e Node, non avvolge mai il suo output a 76 caratteri. Incontra prima la funzione, poi incontra le trappole.

Imballare senza fallire

Il novanta per cento della vita della codifica in Go è un metodo del tipo Encoding, e come ogni punto d'ingresso della codifica nel pacchetto (Encode, AppendEncode), non ha un valore di errore in restituzione:

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

Dagli dei byte, ti dà una stringa, e questo è l'intero contratto:

package main

import (
  "encoding/base64"
  "fmt"
)

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

Non c'è un valore di errore perché non c'è niente che possa andare storto: qualsiasi byte è input legale, l'alfabeto lo copre sempre, e l'output è sempre ASCII puro. Tre proprietà meritano di essere memorizzate, perché rispondono a metà di tutte le domande future. La prima: la lunghezza dell'output è una funzione aritmetica pura della lunghezza dell'input, e il pacchetto ti consegna persino la formula come metodo: EncodedLen(n) restituisce (n+2)/3*4 per le codifiche con padding, quindi 3 byte di input diventano 4 caratteri, 6 diventano 8, e così via. La seconda: il formato porta una tassa sulle dimensioni: ogni tre byte di dati tornano come quattro caratteri, l'espansione familiare di circa il 33 percento che ti compare nelle bollette di banda e nelle quote di storage. La terza: il metodo è deterministico: gli stessi byte producono sempre la stessa stringa, su qualsiasi macchina, in qualsiasi versione di Go, per sempre. È quel determinismo che rende il base64 un formato di serializzazione invece di un mistero.

Una nota specifica di Go sul lato input: il metodo prende un []byte, non una string, e la conversione []byte(...) è esplicita in ogni punto di chiamata - Go non converte mai una stringa in una slice per te - e produce una copia indipendente dei byte della stringa. Il compilatore può elidere quella copia quando la slice viene solo letta e non fa escape, ed è per questo che il costo di solito è inmisurabile; ma se la slice viene immagazzinata o restituita, il runtime paga una copia vera O(n). Il testo in un programma Go è UTF-8 per convenzione, quindi quando codifichi una stringa stai codificando i suoi byte UTF-8, ed è esattamente ciò che ogni decoder moderno dall'altra parte si aspetta. Se ne parla di più nella sezione su Testo, byte e Unicode.

Come lo spedisce Go

Come tutto in questo articolo, l'encoder viene dal pacchetto della libreria standard encoding/base64, che spedisce dalla prima release del linguaggio e il cui file sorgente porta ancora la sua intestazione di copyright 2009. Non c'è un modulo da scaricare, nessuna flag di funzionalità da cambiare, e nessuna stranezza di piattaforma: se go version funziona, go doc encoding/base64 stampa l'API intera per te.

Alla data di questo articolo la release più recente è Go 1.27.1, uscita il 1 settembre 2026, con la linea Go 1.26 (attualmente 1.26.8) come altra linea supportata. Installa Go dai tarball ufficiali su go.dev/dl, dal gestore di pacchetti della tua distribuzione (sudo apt install golang-go), o tramite il wrapper golang.org/dl se giocoli tra le versioni. L'API base64 è identica su entrambe le linee supportate, e la tabella qui sotto è l'intera storia di ciò che è mai cambiato, un elenco corto per un pacchetto così centrale:

Release Anno Cosa è cambiato in encoding/base64
Go 1.0 2012 Pacchetto stabile dal giorno uno; copyright del sorgente 2009
Go 1.5 2015 Aggiunti RawStdEncoding e RawURLEncoding per l'output senza padding
Go 1.8 2017 Aggiunto Strict() per la decodifica canonica (lato decoder)
Go 1.22 2024 Aggiunti AppendEncode e AppendDecode; WithPadding ora rifiuta argomenti sbagliati
Go 1.27.1 2026 Release attuale; API invariata, comportamento stabile a byte per la promessa di Go 1

La conseguenza pratica di quella storia: il codice scritto contro questa API nel 2015 compila e si comporta in modo identico oggi, e le stringhe che il tuo programma codifica nel 2026 si decodificheranno correttamente su ogni release Go, passata o futura. Per un formato di serializzazione, è questo il superpotere silenzioso.

Scegliere un alfabeto per la destinazione

La codifica ha una sola vera decisione, ed è una domanda di viaggio: dove andrà questa stringa? Go ti dà quattro encoder già pronti, e ciascuno è tarato per un canale diverso:

Encoder Alfabeto Padding Invialo lì quando la stringa viaggia attraverso
StdEncoding A-Z a-z 0-9 + / = Body JSON, parti MIME email, data URL, autenticazione HTTP Basic, PEM, la maggior parte delle API
URLEncoding A-Z a-z 0-9 - _ = Percorsi e query degli URL, nomi di file, ovunque + o / richiederebbero un escape
RawStdEncoding A-Z a-z 0-9 + / nessuno Stringhe a alfabeto standard compatte dove il padding non deve comparire
RawURLEncoding A-Z a-z 0-9 - _ nessuno Segmenti JWT, identificatori compatti, token incorporati in URL

Il ragionamento dietro le varianti è il ragionamento dietro il formato stesso. L'alfabeto standard è ciò che MIME e la maggior parte delle API si aspettano, quindi è il predefinito e la risposta sicura quando nessuno ti ha detto altrimenti. L'alfabeto URL-safe esiste perché + e / sono caratteri riservati negli URL: un più in una stringa di query viene spesso letto come spazio, e una barra inizia un nuovo segmento di percorso, quindi il base64 standard in un URL o si rompe o ha bisogno del percent-escaping sui caratteri che portano +, / o = - un paio di percento di un token tipico. Sostituirli con - e _, legali senza escape nei percorsi, nelle query e nei nomi di file, è la correzione che la RFC 4648 ha standardizzato. Le varianti Raw eliminano del tutto i segni uguale finali, il che conta nei contesti dove il padding è o proibito o semplicemente mai usato, come i segmenti JWT. La regola che ti salva dalla maggior parte del debug: l'encoder che scegli e il decoder che usa l'altra parte sono un unico contratto, e il contratto è scritto dalla destinazione, non da te.

Se un sistema con cui parli ha definito un alfabeto privato di 64 caratteri, base64.NewEncoding("...64 chars...") ti costruisce un encoder per esso, e WithPadding(rune) ti lascia scambiare il carattere di padding o disattivarlo con NoPadding. Entrambe le funzioni vanno in panico su argomenti non validi (una lunghezza dell'alfabeto sbagliata, un carattere duplicato, un a capo nell'alfabeto, un carattere di padding in collisione con l'alfabeto), quindi costruisci i tuoi encoder personalizzati una volta, all'avvio, mai in un percorso caldo.

La trappola di Close

Ecco la trappola più famosa di questo pacchetto, e compare solo quando codifichi uno stream invece di una stringa. NewEncoder avvolge qualsiasi io.Writer in uno scrittore che codifica base64, e poiché il base64 lavora a blocchi di tre byte di input che producono quattro caratteri di output, l'encoder deve mettere in buffer i tuoi ultimi uno o due byte, in attesa di vedere se ne arrivano altri. Escono solo quando lo chiudi:

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  -- dove è il "lo"?

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

La prima stampa è tutta la lezione: senza Close, l'encoder ha emesso solo il primo blocco completo, tre byte di "hello" che diventano "aGVs", e i rimanenti due byte sono semplicemente svaniti nel buffer interno. La seconda stampa, dopo Close, è la stringa corretta e completa. La correzione è un'abitudine, non una tecnica: nel momento in cui crei un encoder, crea anche la sua pulizia:

enc := base64.NewEncoder(base64.StdEncoding, w)
defer enc.Close() // ricorda di controllare l'errore restituito nel codice di produzione

Due dettagli rendono questa trappola più affilata di quanto sembri. Il primo: Close fa lavoro vero: scarica il blocco parziale pendente e può fallire, perché scrive sullo scrittore sottostante, quindi la versione idiomatica ne controlla l'errore, soprattutto quando la destinazione è una rete o un disco. Il secondo: la documentazione dice che è un errore chiamare Write dopo Close, ma il runtime non impone quella frase. Se scrivi di nuovo dopo aver chiuso, l'encoder inizia in silenzio un blocco fresco e lo aggiunge, producendo una stringa con il padding in mezzo, che è base64 non valido che la maggior parte dei decoder rifiuterà con un offset confuso. Il contratto tocca a te custodirlo.

Avvolgimento di righe, alla Go

Ogni altra grande implementazione base64 che hai mai usato avvolge il suo output: il MIME vuole righe di al massimo 76 caratteri, il PEM usa 64, i client di posta di tutto il mondo inseriscono un CRLF ogni tanto. L'encoder di Go non fa nessuna di queste cose. Emette una riga continua, per quanto grande sia il payload, e lo fa dalla nascita del pacchetto. L'output per un megabyte di dati è una singola riga di un megabyte e un terzo, dall'inizio alla fine, senza interruzioni.

È una scelta deliberata, non una dimenticanza. Il formato funziona identico con o senza a capo, il decoder stesso di Go li salta ovunque nell'input, e un encoder che inserisse in silenzio dei CRLF nei tuoi dati sorprenderebbe i programmi che immagazzinano la stringa in una colonna del database o la confrontano per uguaglianza. Il costo è che devi avvolgere da solo quando il canale lo richiede, ed è un piccolo helper:

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))
}

Una nota sulla direzione di viaggio: poiché il decoder di Go ignora gli a capo ovunque, l'input avvolto si decodifica perfettamente sul lato Go di qualsiasi ponte. È nell'altra direzione che serve attenzione: se invii un output avvolto a un consumatore che non si aspetta interruzioni (un campo JSON, un URL, un token), rimuovili prima, perché quel consumatore può trattare un a capo come un carattere corrotto. Sappi in quale convenzione vive il tuo canale, ed emettila di proposito.

Imballare per email e MIME

L'email è la casa più antica del base64. Il protocollo SMTP originale era progettato per trasportare ASCII a 7 bit, quindi gli allegati venivano codificati in base64 prima dell'invio e decodificati all'arrivo, e lo standard MIME (RFC 2045) ha formalizzato la pratica: l'header Content-Transfer-Encoding: base64 marca una parte, e il corpo dovrebbe essere spezzato in righe di al massimo 76 caratteri con CRLF tra di esse.

Il pacchetto net/smtp di Go invia i byte che gli dai, e non ti costruirà le parti MIME, quindi in un programma che compone email la parte base64 si presenta così:

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())
}

Tre cose da notare. L'encoder standard è quello giusto qui, perché il MIME è il contesto originale dell'alfabeto standard. Le interruzioni di riga sono CRLF, non l'a capo nativo della piattaforma, perché è ciò che specifica la RFC e ciò che i parser di posta si aspettano. E se il tuo programma invia email vere in volume, una libreria MIME mantenuta ti costruirà il messaggio intero; il punto di questo esempio è la metà base64, che è la parte che spetta a questo pacchetto. Metti a posto l'alfabeto e la convenzione di righe, e il resto del MIME è il problema di un altro.

Imballare i file

Per i file che stanno in memoria, il pattern sono le stesse due righe di qualsiasi altro posto: leggi, poi EncodeToString. Per quelli che non stanno, lo streaming tiene piatta la memoria, e la ricetta è un file, un encoder, una copia, e due chiusure nell'ordine giusto:

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) // scarica l'ultimo blocco parziale
}
if err := out.Close(); err != nil {
  panic(err)
}

L'ordine delle chiusure è la parte sottile, ed è la versione per file della trappola di Close: l'encoder deve essere chiuso prima del file, perché enc.Close è ciò che scrive l'ultimo blocco parziale nel file, e chiudere prima il file lascerebbe quel blocco in un buffer che scrive nel nulla. Con defer, ricorda che le chiamate differite si eseguiscono in ordine inverso, quindi registrare out.Close per primo e enc.Close per secondo (o, come nell'esempio sopra, chiudere l'encoder esplicitamente prima di differire il file) è ciò che rende la sequenza sicura.

Tieni la tassa sulle dimensioni in testa quando pianifichi intorno a questo pattern: una foto da 10 megabyte diventa circa 13,3 megabyte di testo, e un archivio da 100 megabyte diventa una stringa da 133 megabyte su disco. Se la destinazione ha una quota, un limite o un prezzo per byte, è la versione base64 del tuo file a essere contata, non l'originale.

Imballare per il web: data URL

I browser caricano volentieri un'immagine o un font da una stringa che vive dentro l'HTML o il CSS stesso, e quella stringa è una data URL: il media type, la flag ;base64, una virgola, e il payload, tutto in un solo URL. Go non ha un helper per le data URL, ma costruirne una è concatenazione di stringhe, perché il formato è un contratto che puoi vedere scritto nero su bianco:

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...
}

Due regole tengono le data URL lontane dai guai. Includi sempre il media type: nella grammatica è opzionale (il predefinito è text/plain;charset=US-ASCII), ma un browser che indovina il tipo del tuo payload binario non è uno scenario che vuoi. E tratta le data URL come un trucco per asset piccoli. La RFC dice che lo scheme è utile solo per valori corti, e l'espansione del 33 percento è ciò che fa la differenza tra un'icona da 2 kilobyte che risparmia una richiesta e una foto da 5 megabyte che appesantisce ogni caricamento di pagina, senza cache su cui condividerla e senza URL da passare a qualcuno. Icone, favicon, sprite piccoli: sì. Foto di prodotto: no.

Imballare per HTTP

Tre contesti HTTP dominano il base64 nei servizi Go, e due di essi vengono con aiuto integrato. Il primo è il body JSON, il cavallo di battaglia: codifichi un valore prima della serializzazione, e il campo porta una stringa piatta attraverso il cavo:

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 un tipo compare in molti posti, la mossa Go pulita è implementarci MarshalJSON e UnmarshalJSON, così il passo base64 è invisibile a ogni punto di chiamata. Il secondo contesto è l'autenticazione HTTP Basic, dove la libreria standard fa tutto il lavoro: Request.SetBasicAuth(user, pass) costruisce l'header Authorization per te, facendo girare l'encoder standard sulla coppia user:pass che specifica la RFC 2617. La regola lì è una sola: non improvvisare. La Basic auth è base64 standard con un prefisso Basic , e un alfabeto URL-safe o un segno di padding mancante trasformeranno un login funzionante in un 401 che nessuno saprà spiegare.

Il terzo contesto sono gli URL, dove la stringa è il payload di un segmento di percorso o di un parametro di query. Qui l'alfabeto standard è una scelta pessima, perché +, / e = collidono tutti con la grammatica degli URL, e ogni loro occorrenza ha bisogno di un percent-escape. Codifica invece con la variante URL-safe, e il token sopravvive all'URL intatto. Se il consumatore lo percent-escappa comunque, niente si rompe, ma se non lo fa, ti sei risparmiato una classe di 404.

Output URL-safe

Il base64 URL-safe merita la sua sezione in Go perché è la variante a cui ricorrerai più spesso che non alla standard, e perché Go rende il cambio gratuito. L'alfabeto alternativo della RFC 4648 sostituisce + con - e / con _, quindi l'output non ha bisogno di escape nei percorsi degli URL, nelle query o nei nomi di file, e si legge come un singolo token pulito in una riga di log. I due encoder già pronti sono URLEncoding (con padding) e RawURLEncoding (senza 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

Quel singolo input, tre output: la versione standard ha bisogno di un percent-escape per il suo segno più, la versione URL-safe è un solo token, e la versione raw elimina anche il padding. I lavori Go tipici per ciascuna: identificatori opachi che un servizio genera e poi immagazzina in URL, route o nomi di file; token API che i client incollano nelle stringhe di query; qualsiasi cosa che comparirà in una riga di log dove un più o una barra sono a un carattere dall'essere scambiati per sintassi.

La disciplina che tiene pulito questo aspetto è la stessa di tutto l'articolo: la variante è un contratto con il consumatore. Se l'altra parte si aspetta base64 standard e tu mandi URL-safe, il suo decoder fallisce al primo trattino, e l'errore sarà un offset di byte vicino alla fine di una stringa perfettamente sana, che non è una cosa ovvia da debuggare. Quando hai dubbi, chiedi cosa si aspetta l'altra parte, leggi la specifica a cui punta, e scegli l'encoder dalla destinazione, non dall'abitudine.

Imballare i JWT

I JSON Web Token sono il consumatore di base64 più visibile nelle API moderne, e fissano la variante esatta: la serializzazione compatta JWS, secondo la RFC 7515, è tre segmenti base64url senza padding, uniti da punti. Header, payload, firma. Questo significa che l'encoder della scelta per qualsiasi cosa tu costruisca a mano è 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)
}

Leggi quell'esempio come una lezione su ciò che il formato è, non come una raccomandazione a metterlo in produzione: mostra esattamente dove sta il base64 (due volte prima della firma, una volta dopo) e perché la firma copre i segmenti codificati, non il JSON grezzo. In produzione, firma e verifica con una libreria mantenuta, perché il JWT ha una lunga coda di errori (scarto dell'orologio sull'espiry, confusione di algoritmo, controlli di audience mancanti) che il livello base64 non può vedere. La libreria Go de facto è github.com/golang-jwt/jwt/v5, installata con 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)
}

La libreria esegue la codifica base64url di ogni segmento internamente, quindi non tocchi encoding/base64 per niente, che è l'esito migliore: un posto in meno dove un errore di padding o di alfabeto può nascondersi. E nota la guardia che ti dà gratis: la v5 rifiuta i token che dichiarano alg=none a meno che tu non opti esplicitamente con la sua costante UnsafeAllowNoneSignatureType, che è la protezione che vuoi senza doverci pensare.

Testo, byte e Unicode

La posizione di Go su questa domanda è la più corta di qualsiasi linguaggio importante, ed è il motivo per cui il base64 è così piacevole qui: una string in Go è una sequenza di byte in sola lettura, e il testo nel tuo programma è UTF-8. Non c'è un livello di codifica nascosto, nessuna sorpresa tipo "la stringa è in realtà UTF-16", e nessuna flag di charset da impostare. Quando scrivi EncodeToString([]byte(myText)), stai codificando i byte UTF-8 del testo, punto e basta:

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

Quella riga è tutta la storia per il testo moderno, emoji e CJK compresi: il base64 opera sui byte, l'UTF-8 è solo una sequenza di byte, e ogni decoder dall'altra parte che segue la stessa convenzione ti restituirà la stessa stringa. La conversione []byte(...) è una copia indipendente, che il compilatore elide quando la slice viene solo letta e non fa escape - quindi in pratica non costa nulla di misurabile.

L'unico caso in cui la storia si allunga sono i dati legacy: byte prodotti da un sistema Windows-1252, Shift JIS o ISO-8859-1 che non sono UTF-8 valido. Se codifichi in base64 quei byte così com'è, hai trasportato fedelmente del testo rotto, che non è ciò che voleva nessuno. La correzione è normalizzare prima di codificare, usando golang.org/x/text, così la stringa base64 porta UTF-8 pulito dal momento in cui lascia il tuo programma:

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

legacy := []byte{0x43, 0x61, 0x66, 0xE9} // "Café" in Windows-1252
utf8, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), legacy)
if err != nil {
  panic(err)
}
packed := base64.StdEncoding.EncodeToString(utf8)
// Q2Fmw6k=  -- lo stesso "Café", ora byte UTF-8 puliti pronti a viaggiare

Lo stesso modulo copre japanese, korean, simplifiedchinese e traditionalchinese oltre a charmap. La regola pratica: converti una volta, al confine dove i byte legacy entrano nel tuo programma, e da quel momento tutto ciò che codifichi è UTF-8. Non convertire due volte, non indovinare, e non lasciare mai che un payload non UTF-8 si infili in una stringa base64 che un consumatore moderno decodificherà e mostrerà.

Misurare l'encoder

L'encoder è una ricerca in tabella senza ramificazioni sull'input e senza allocazione oltre alla stringa di output, e si vede nei numeri. Su una CPU desktop recente con Go 1.26, codificare 500 byte richiede circa tre decimi di microsecondo con due allocazioni, il che fa venire fuori un ordine di grandezza di un gigabyte e mezzo al secondo. Un megabyte di dati si codifica in molto meno di un millisecondo; l'encoder raramente sarà qualcosa che puoi sentire.

L'unica leva da conoscere è il profilo di allocazione nei loop caldi. EncodeToString alloca la stringa di output ad ogni chiamata, che è il compromesso giusto per il caso del 99 percento. Se stai codificando migliaia di blocchi al secondo in un buffer che cresce, AppendEncode, aggiunto nel Go 1.22, aggiunge i byte codificati a una slice che riutilizzi ed esegue nessuna allocazione in regime stazionario una volta che il buffer è cresciuto a dimensione:

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

Usa EncodeToString per le operazioni una tantum, AppendEncode per i loop stretti, e NewEncoder per stream e file. Quale che sia la scelta, ricorda che la rete o il disco intorno all'encoder è quasi sempre la parte lenta, quindi fai il profiling dell'intero percorso prima di ottimizzare l'alfabeto.

Considerazioni di sicurezza

La frase di sicurezza più importante di questo articolo: il base64 non è cifratura, e "lo codifichiamo in base64 prima" non è una misura di sicurezza. L'alfabeto rende i dati sicuri per il testo, non segreti, e chiunque con gli strumenti per sviluppatori di un browser può leggere il tuo base64 in un istante. La riservatezza viene dal TLS e dal controllo degli accessi, e il lavoro del base64 è portare i byte attraverso un canale solo-testo senza corromperli. Tieni quei due lavori separati nel tuo design e nella tua documentazione, ed eviti il classico commento di revisione "la password è protetta, guarda, è base64".

La seconda considerazione è la dimensione. Poiché il formato si espande di un terzo, ogni limite nel tuo sistema ha una versione base64: un'API che accetta 4 megabyte di JSON accetta circa 3 megabyte di dati originali quando il payload è un campo base64, un URL con un budget di lunghezza diventa più corto in byte grezzi quando il token è URL-safe e senza padding, e una colonna del database dimensionata per il valore grezzo può essere troppo piccola per quello codificato. Fai l'aritmetica con EncodedLen prima di immagazzinare, inviare o limitare, e ricorda che l'espansione è sull'input con cui parti, non sulla stringa con cui finisci.

Terza cosa: pensa a dove la stringa codificata può essere osservata. Le stringhe base64 sono amiche dei log e degli schermi, ed è una caratteristica, finché un allegato da 20 megabyte non si codifica in 26 megabyte di testo che il tuo log di accesso registra diligentemente a ogni richiesta. Registra la lunghezza, i primi due o tre dozzine di caratteri, e l'identificatore, non il payload, e i tuoi log restano leggibili e il tuo disco vivo. Infine, negli URL, preferisci la variante URL-safe così i tuoi token non spendono nessuno dei loro caratteri come percent-escape, che appesantisce l'URL e ogni tanto fa inciampare un gateway o un proxy con un'idea rigorosa di ciò che appartiene a una stringa di query.

Fatti curiosi e stranezze Go

Qualche fatto specifico di questo pacchetto, per i momenti in cui vuoi avere ragione in una revisione del codice:

  • EncodeToString è il cavallo di battaglia, e come ogni punto d'ingresso della codifica nel pacchetto (Encode, AppendEncode), non ha un valore di errore in restituzione - la codifica non può fallire in Go, che è una specie rara e silenziosa di libertà: qualsiasi byte è input legale, e l'unico modo per ottenere una stringa sbagliata è scegliere l'alfabeto sbagliato per il canale.
  • EncodedLen è aritmetica pura, (n+2)/3*4 per le codifiche con padding, calcolata senza allocazione e senza loop. Esiste perché tu possa dimensionare buffer e quote senza mai codificare un byte.
  • L'encoder di stream interno nasconde un buffer di input da 3 byte e un buffer di output da 1024 byte, ed è per questo che NewEncoder scrive a blocchi e per questo l'ultimo blocco parziale può uscire solo attraverso Close. I buffer sono la ragione della trappola.
  • La documentazione dice che è un errore scrivere dopo aver chiamato Close, ma il runtime non impone la frase. Un Write tardivo viene accettato, aggiunge un blocco fresco, e produce una stringa con il padding in mezzo: base64 non valido, generato con garbo, senza un valore di errore all'orizzonte.
  • L'encoder di Go non ha mai avvolto il suo output a 76 caratteri - come gli encoder di Python, Java e Node, l'encoder di Go produce una riga per un megabyte di dati. Il tuo helper di avvolgimento MIME è un progetto personale, il che è anche un buon modo per ricordare che le interruzioni di riga nel base64 delle email sono una convenzione MIME, non un requisito del base64.
  • A agosto 2026, più di 244.000 pacchetti pubblici su pkg.go.dev importano encoding/base64. Qualsiasi cosa sia il tuo programma Go, è quasi certo che faccia base64 da qualche parte, che tu lo sappia o no.
  • La promessa di compatibilità di Go 1 si applica a questo pacchetto con una forza speciale: l'output di un programma che ha codificato una stringa nel 2013 è identico a byte sul Go 1.27 di oggi. Le stringhe base64 sono, in Go, effettivamente immortali.

Gli errori che continuano a riaffiorare

Gli errori di codifica che continuano a riaffiorare nei codebase Go, più o meno nell'ordine in cui arrivano:

  • Dimenticare Close sull'encoder di stream, e spedire una stringa che manca gli ultimi uno o due byte. Il bug sopravvive a ogni test che usa input la cui lunghezza è un multiplo di tre, ed è così che arriva in produzione.
  • Chiudere il file prima dell'encoder, così l'ultimo blocco parziale viene scaricato su un handle di file che è già andato. L'output è troncato di esattamente la stessa quantità, e l'errore compare solo su input di dimensioni dispari.
  • Aspettarsi interruzioni di riga da 76 caratteri nell'output MIME o email e restare confusi quando Go ti consegna una riga lunghissima. L'avvolgimento è una convenzione del canale, e in Go è compito del tuo codice applicarla.
  • Usare l'alfabeto standard dentro gli URL, poi passare un pomeriggio a rincorrere 404 e 400 che in realtà sono un problema di percent-encoding. Se la stringa dovrà vivere in un URL, parti da URLEncoding o RawURLEncoding.
  • Emettere padding dove il consumatore lo vieta: segmenti JWT, alcuni formati di token, alcuni parser rigorosi. Le varianti raw esistono esattamente per questo, e il messaggio di errore dall'altra parte è spesso un offset di byte proprio alla fine della tua stringa.
  • Codificare in base64 un segreto e chiamarlo protezione. Non lo è. L'header, il token, il campo "cifrato": leggibili da chiunque in mezzo secondo. Usa il TLS, usa l'hashing dove è l'hash a volere il protocollo, e lascia al base64 il suo unico lavoro onesto.
  • Dimenticare il 33 percento quando imposti i limiti: dimensioni dei body, larghezza delle colonne, budget degli URL, controlli delle quote. L'aritmetica è una chiamata a EncodedLen, e il costo di saltarla è un 413 o una colonna troncata in produzione.
  • Codificare testo che non è UTF-8, il che trasporta fedelmente la rottura. Normalizza i charset legacy con golang.org/x/text prima di codificare, così la stringa base64 porta byte puliti.
  • Scrivere sull'encoder dopo averlo chiuso, per abitudine o da un loop di retry. Nessuna eccezione viene sollevata, e l'output è silenziosamente non valido.
  • Dare per scontato che il decoder dall'altra parte sia permissivo come quello di Go. Go salta gli a capo ovunque, ma altri linguaggi e parser sono più rigorosi sugli spazi e sulla lunghezza delle righe, quindi allineati alla convenzione del canale, non all'umore del runtime Go.

Il lato opposto

Questa è la parte codifica della storia: un metodo che non può fallire, quattro encoder abbinati ai canali attraverso cui le loro stringhe viaggeranno, un encoder di stream con un Close obbligatorio, e un formato che espande i tuoi dati di un terzo e non avvolge mai, mai le sue righe. Scegli l'alfabeto dalla destinazione, chiudi i tuoi encoder, fai l'aritmetica delle dimensioni in anticipo, e il base64 in Go resta l'utility silenziosa e a zero dipendenze che è dal 2009.

E quando il traffico si inverte, quando il tuo programma riceve una di queste stringhe e deve aprirla, l'articolo correlato sulla decodifica Base64 in Go copre quel lato in dettaglio: le regole di tolleranza del decoder, gli offset di errore che ti dicono il byte dove l'input va storto, la modalità rigorosa per i protocolli schizzinosi, e le stesse quattro codifiche viste dall'altra direzione.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in Go: una guida completa