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 Python: una guida completa

Ecco l'altra faccia della medaglia. Hai dati in mano - un file, una coppia di credenziali, un blob binario, un paragrafo di Unicode - e in qualche punto a valle devono attraversare un canale che non accetta altro che lettere. È questo l'intero lavoro di Base64: riscrivere tre byte di dati come quattro caratteri di un alfabeto di 64 caratteri, completare l'ultimo gruppo con = così che tutto esca a gruppi di quattro, e consegnare le lettere. La home page di questo sito spiega il formato per intero, alfabeto incluso, quindi ci limiteremo a un respiro su quello e spenderemo il resto del tempo su cosa fa davvero Python con esso.

La questione economica merita una frase onesta prima di cominciare, perché il numero esce in ogni conversazione sull'argomento: il prezzo di quella sicurezza da testo è la dimensione. Base64 espande i tuoi dati di circa un terzo, quattro caratteri per ogni tre byte in ingresso, quindi un megabyte di binario diventa un megabyte e un terzo di lettere. Per un token o un valore di configurazione è una non questione; per un file video è la ragione per cui dovresti pensare alle tue opzioni.

E la buona notizia: la risposta di Python a tutto questo è un import e una funzione. base64.b64encode è nella libreria standard da decenni, non richiede installazione e corre a velocità C sotto il cofano. Il resto di questa guida è la coda lunga che rende utile la versione in una riga nel mondo reale: la regola dei soli byte che ferma metà di tutti i bug TypeError, l'alfabeto URL-safe, gli strumenti di a capo MIME, e i protocolli - JWT, header HTTP, handshakes WebSocket, email, PEM, data URL - dentro i quali Base64 fa il suo lavoro in silenzio.

Incontriamo b64encode

Il contratto sta in quattro frasi. Una: l'input è un oggetto bytes-like - bytes, bytearray, memoryview - e una stringa semplice viene rifiutata. Due: l'output è un oggetto bytes, mai una str. Tre: l'output è sempre riempito fino a un multiplo di quattro caratteri, così anche un singolo byte in ingresso produce Zg==. Quattro: l'output è una singola riga, mai avvolta, per quanto grande sia l'input. Tutto il resto di questo articolo è commento su quelle quattro frasi:

import base64
encoded = base64.b64encode(b"foobar")
print(encoded)
# b'Zm9vYmFy'
print(len(encoded))
# 8

Per ottenere una stringa vera e propria, per un URL o un header o un campo JSON, decodifica il risultato come ASCII. L'alfabeto garantisce che non possa esserci altro dentro, il che rende questo passaggio sicuro e poco costoso:

import base64
text = base64.b64encode(b"foobar").decode("ascii")
print(text)
# Zm9vYmFy

C'è un argomento in più sulla firma, altchars, e sostituisce il + e il / dell'alfabeto standard con una diversa coppia di caratteri. È esattamente la manopola dietro la variante URL-safe, quindi tieni in mente questo pensiero - lo incontrerai tra qualche sezione, quando parleremo di token e stringhe di query.

Il muro di tipi: str non è bytes

Il primo muro specifico di Python in questo articolo è il sistema di tipi, e vale la pena imparare a sentirlo. b64encode rifiuta le stringhe con uno dei messaggi di errore meno indulgenti del linguaggio:

import base64
try:
  base64.b64encode("hello")
except TypeError as caught:
  print(caught)
# a bytes-like object is required, not 'str'

La correzione è l'abitudine singola più importante di questa intera guida: trasforma il tuo testo in byte prima, e scegli la codifica con deliberazione invece di sperare:

import base64
print(base64.b64encode("été".encode("utf-8")))
# b'w6l0w6k='
print(base64.b64encode("été".encode("utf-16")))
# b'//7pAHQA6QA='

Stessi caratteri, due stringhe di byte diverse, due output Base64 diversi. La scelta della codifica è una decisione, non un dettaglio. UTF-8 è il default per tutto ciò che attraverserà un cavo, un database o un'API. UTF-16 fa capolino quando parli alle API di Windows, e porta un byte order mark in testa che potresti non voler codificare, che puoi scartare usando utf-16-le o tagliandolo con lstrip("\ufeff"). Latin-1 sta ancora nascosto in vecchi file europei, dove un carattere è esattamente un byte e l'intera questione non si pone nemmeno. Il modello mentale da tenere: il codificatore non guarda mai al tuo testo, vede solo bit. Nel momento in cui i byte attraversano il muro, la questione della codifica dei caratteri è chiusa - ed è anche per questo che il lato decodifica dovrà chiedere, più tardi, a chi apparteneva quella codifica.

base64url: scambia due lettere, butta via il riempimento

Nell'alfabeto standard sono nascosti due caratteri che URL e sistemi di file odiano. Il segno + viene letto in silenzio come spazio da qualsiasi decoder di moduli, e il segno / è un separatore di percorso, quindi un carico utile dell'alfabeto standard dentro una stringa di query o un nome file è una bomba a orologeria. La sezione 5 di RFC 4648 definisce la correzione: una variante in cui + diventa - e / diventa _, in cui il riempimento viene buttato via ogni volta che la lunghezza dei dati è nota dal contesto, e che l'RFC insiste a chiamare base64url e non semplicemente "base64". La incontrerai nei JSON Web Token, nei token OAuth e nei parametri cursore delle API, vale a dire, nella maggior parte del web moderno.

Python fornisce sia una funzione dedicata sia la manopola altchars della prima sezione, e producono output identici:

import base64
data = b"\xfb\xff\xfe"
print(base64.b64encode(data))
# b'+//+'
print(base64.urlsafe_b64encode(data))
# b'-__-'
print(base64.b64encode(data, altchars=b"-_"))
# b'-__-'

Nei token e nelle stringhe di query di solito va via anche il riempimento, perché un = finale avrebbe bisogno di codifica percent e alcuni middlebox lo rovinano comunque:

import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'

Taglia, invia, e il ricevente rimette i riempimenti con il trucco del modulo, "=" * (-len(s) % 4), che produce esattamente tanti riempimenti quanti ne richiede la lunghezza. La regola pratica: se i dati staranno in un URL, un nome file o un JWT, usa la variante urlsafe e butta via i riempimenti; se staranno in un corpo email o in un file di testo, l'alfabeto standard con il suo riempimento è la norma.

Quando il tuo lettore vuole righe: MIME e la regola dei 76 caratteri

La riga unica e infinita di b64encode è perfetta per campi JSON, header e URL, ma la posta elettronica ha le sue opinioni. RFC 2045, lo standard MIME, richiede che l'output Base64 sia spezzato in righe di al massimo 76 caratteri, e gli strumenti legacy di Python sono stati costruiti per produrre esattamente quello. encodebytes, aggiunto in Python 3.1, fa l'avvolgimento per un oggetto byte:

import base64
wrapped = base64.encodebytes(b"x" * 100)
for line in wrapped.splitlines():
  print(len(line), line[:12])
# 76 eHh4eHh4eHh4
# 60 eHh4eHh4eHh4

I meccanismi sono un po' carini. Il modulo codifica in blocchi da 57 byte, la costante MAXBINSIZE, perché 57 byte diventano esattamente 76 caratteri, e nel CPython moderno ogni riga avvolta finisce con un semplice line feed. RFC 2045 chiedeva CRLF, ma l'output LF di Python è accettato da ogni decoder dell'ecosistema, incluso quello di Python stesso. La funzione legacy da file a file encode fa lo stesso avvolgimento direttamente da un handle file a un altro, il che la rende uno strumento ordinato per i file grandi che non vuoi tenere in memoria due volte.

Quale strumento quando, in breve: b64encode per tutto ciò che va in un campo JSON, un URL, un header o una colonna del database; encodebytes per corpi email e armature in stile PEM; la legacy encode quando stai facendo streaming di un file grande e vuoi l'avvolgimento gratis. Scegliere quello sbagliato è un bug classico, perché un singolo a capo vagante dentro un campo JSON basta a far lanciare un'eccezione a un decoder rigoroso dalla parte opposta.

La famiglia allargata

Il modulo base64 è in realtà il modulo base-N, e porta con sé tutta la famiglia RFC 4648 più un paio di parenti da altri angoli del mondo informatico. La maggior parte sono sostituti in una riga per lo stesso contratto byte-in-byte-out:

Funzioni Alfabeto Quando lo incontri
b16encode / b16decode 0-9A-F "Base16" è semplicemente esadecimale; il giro più veloce del modulo, ottimo per hash e UUID
b32encode / b32decode A-Z2-7 chiavi di licenza e codici di attivazione; niente 0, O, 1 o I, così sopravvive a essere letto ad alta voce
b32hexencode / b32hexdecode 0-9A-V Base32 con alfabeto esadecimale, aggiunto in Python 3.10; mantiene i dati codificati ordinabili in ordine lessicografico
a85encode / a85decode 85 caratteri stampabili ASCII85 di PostScript e PDF, il discendente dell'utilità Unix btoa; nel modulo da Python 3.4
b85encode / b85decode 85 caratteri stampabili il formato Base85 usato dai diff binari di git e Mercurial; anche da Python 3.4
z85encode / z85decode 85 caratteri stampabili Z85 di ZeroMQ, aggiunto in Python 3.13; incornicia i dati in gruppi di quattro byte

Nessuno di loro cambia le regole che hai già imparato: byte dentro, byte fuori, un alfabeto da scegliere, e una funzione di decodifica corrispondente in attesa dall'altra parte. Nella pratica ricorrerai a b16 ogni volta che un umano dovrebbe poter leggere il valore, a b32 quando il valore sarà battuto o detto a mano, e ai cugini a 85 caratteri solo quando una specifica te lo dice. Per tutto il resto, la coppia Base64 dall'inizio di questo articolo è lo strumento giusto, ed è quella su cui si costruisce ogni altra parte.

Immagini nella pagina: data URL

Il Base64 più visibile sul web è l'URI data:: media incorporati direttamente in HTML o CSS, così il browser non deve sparare una seconda richiesta. Il formato è data:, il tipo di media, la parola base64, una virgola e i byte codificati. Costruirlo da un file su disco richiede tre righe:

import base64
with open("logo.png", "rb") as handle:
  encoded = base64.b64encode(handle.read()).decode("ascii")
uri = "data:image/png;base64," + encoded
print(uri[:40])
# data:image/png;base64,iVBORw0KGgoAAAAN...

Due avvertenze, entrambe poco costose da osservare. Prima, il browser renderà di buon grado un data URI, e terrà di buon grado megabyte di questi nel documento: per qualsiasi cosa oltre qualche kilobyte, una richiesta d'immagine normale con un header di cache adeguato vince su ogni metrica che conta. Secondo, il tipo di media dopo i due punti è una promessa. Se i byte sono un JPEG, l'URI dice image/jpeg, perché alcuni strumenti validano la coppia e alcuni renderer si rifiutano semplicemente di indovinare. Il passaggio .decode("ascii") non è decorazione nemmeno; senza stai concatenando un oggetto bytes a una stringa e raccogliendo un TypeError, il muro di tipi che fa il suo giro.

Token che puoi consegnare: JWT

Un JSON Web Token è tre pezzi base64url uniti da punti: un header, un carico utile e una firma. Se stai emettendo token veri, non costruire i pezzi a mano. Installa PyJWT (pip install pyjwt) e lascialo costruire le parti base64url, il riempimento e la firma in una chiamata:

import jwt
# Una chiave sotto i 32 byte riceve l'InsecureKeyLengthWarning di PyJWT, un brontolio legittimo per una chiave da demo.
token = jwt.encode(
  {"sub": "1234567890", "name": "John Doe"},
  "super-secret-key",
  algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>

Sotto il cofano PyJWT fa esattamente quello che questo articolo descrive: serializza in JSON, lo fa passare per il codificatore urlsafe e toglie i riempimenti, secondo la definizione JWS in RFC 7515. Se ti serve mai assemblare un pezzo a mano, per una fixture di test o una sessione di debug, la ricetta è la stessa aritmetica ovunque:

import base64
import json
payload = json.dumps({"sub": "1234567890"}).encode("ascii")
part = base64.urlsafe_b64encode(payload).rstrip(b"=")
print(part)
# eyJzdWIiOiAiMTIzNDU2Nzg5MCJ9

Una nota sulla direzione della fiducia: costruire un token è la metà facile. Il ricevente deve verificare la firma prima di fidarsi di un singolo claim, e PyJWT 2.x non decodifica un token senza un elenco esplicito di algorithms, il che è una funzionalità, perché l'errore di "qualsiasi algoritmo" è una delle righe di codice di autenticazione più costose mai scritte.

HTTP: autenticazione Basic e handshake WebSocket

Due momenti HTTP vivono o muoiono col Base64. Il primo è il meccanismo di autenticazione più vecchio del protocollo: l'autenticazione Basic (RFC 7617), dove il client invia user:pass, codificato in base64, dietro la parola Basic:

import base64
credentials = base64.b64encode(b"jane:pa:ss").decode("ascii")
header = "Basic " + credentials
print(header)
# Basic amFuZTpwYTpzcw==

Se requests è già nel tuo stack, costruisce questo header per te con auth=("jane", "pa:ss"), che vale la pena usare perché tiene il dettaglio della codifica fuori dal tuo codice. E sii onesto su cosa sta succedendo mentre ci sei: RFC 7617 è secco nel dire che il meccanismo "non è un metodo sicuro di autenticazione dell'utente, né protegge in alcun modo l'entità, che viene trasmessa in chiaro". Le credenziali sono recuperabili in una riga di codice da chiunque veda il traffico, quindi questa è una comodità per le connessioni protette da TLS, non un confine di sicurezza.

Il secondo momento è l'handshake WebSocket (RFC 6455), dove il server dimostra di aver letto la chiave casuale del client rispondendo con il Base64 di un hash SHA-1 della chiave incollata a un GUID magico:

import base64
import hashlib
key = "dGhlIHNhbXBsZSBub25jZQ=="
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
  hashlib.sha1((key + magic).encode("ascii")).digest()
).decode("ascii")
print(accept)
# s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

L'output è il valore esatto dell'esempio svolto nell'RFC stesso, il che è un modo delizioso per controllare un'implementazione da zero. In produzione la libreria websockets fa questo passaggio per te su entrambi i lati; lo costruisci a mano solo quando stai scrivendo il piccolo server di test che prova la tua comprensione.

La posta elettronica: il cliente originale

Base64 è stato standardizzato nel 1993 per un lavoro, e quel lavoro era la posta elettronica: far sopravvivere il binario al mondo solo-testo dello SMTP, secondo il Content-Transfer-Encoding: base64 di RFC 2045. Il pacchetto email di Python costruisce il messaggio, sceglie la codifica e avvolge il corpo alla lunghezza di riga standard senza che tu debba scrivere una riga di Base64:

import email.mime.multipart
import email.mime.application
msg = email.mime.multipart.MIMEMultipart()
msg["Subject"] = "binary payload"
part = email.mime.application.MIMEApplication(
  b"\x00\x01\x02", _subtype="octet-stream"
)
msg.attach(part)
text = msg.as_string()
print(text)
# ...
# Content-Transfer-Encoding: base64
#
# AAEC
# --...

La parte MIMEApplication è quella interessante: avvolge i byte nelle corrette righe da 76 caratteri e timbra l'header di transfer-encoding, che è esattamente il comportamento di encodebytes visto prima applicato dal framework. Se stai assemblando un frammento nudo invece di un messaggio completo, email.encoders.encode_base64(obj) fa una singola codifica-e-avvolgimento direttamente su un oggetto messaggio. E quando il messaggio arriva dall'altra parte, il lato decodifica della storia, da get_payload(decode=True) alla decodifica degli header, è coperto nell'articolo correlato sulla decodifica.

L'armatura PEM per chiavi e certificati

I file PEM - i certificati, le chiavi private e i CRL che aprono con -----BEGIN ...----- - sono nient'altro che armatura attorno al Base64: una riga di etichetta, Base64 avvolto, un'etichetta di chiusura. L'armatura è facile da vedere attraverso, perché il corpo è semplicemente l'output avvolto che hai già incontrato:

import base64
der = b"\x30\x03\x02\x01\x05"   # un minuscolo blob DER a scopo illustrativo
body = base64.encodebytes(der).decode("ascii")
armor = ("-----BEGIN CERTIFICATE-----\n"
         + body
         + "-----END CERTIFICATE-----")
print(armor)
# -----BEGIN CERTIFICATE-----
# MAMCAQU=
# -----END CERTIFICATE-----

Togli le due righe di etichetta, unisci il resto, e b64decode ti restituisce i byte DER. In produzione quasi non lo fai a mano: il pacchetto cryptography (pip install cryptography) genera l'armatura con public_bytes e la analizza con load_pem_x509_certificate e compagni, facendo il passaggio Base64 per te sotto il cofano. La via manuale si fa valere nel momento specifico in cui i byte DER grezzi sono già nelle tue mani - una colonna del database, un file di configurazione, un buffer da un protocollo - e la specifica davanti a te dice "PEM, per favore".

Spedire file: upload, download e l'abitudine del .b64

Il caso d'uso più vecchio su internet è un file binario che deve attraversare un canale che trasporta solo testo: un FTP che rovina gli a capo, un modulo che rifiuta gli upload, una finestra di chat che mangia il binario. La ricetta è leggi, codifica, spedisci, e lascia che la parte opposta decodifichi, e la metà interessante sta nei due passaggi centrali:

import base64
with open("photo.png", "rb") as src:
  data = src.read()
wrapped = base64.encodebytes(data)
with open("photo.b64", "wb") as dst:
  dst.write(wrapped)
print(len(wrapped), "bytes on disk for", len(data), "in the photo")
# circa un terzo più grande dell'originale

Due note. L'estensione .b64 è una convenzione della community, non uno standard, quindi il lato ricevente deve conoscere la convenzione anch'esso - ed è per questo che le API JSON di solito avvolgono il carico utile in un campo nominato come "image_base64" e lo dicono nella loro documentazione. E le righe avvolte da 76 caratteri di encodebytes sono il formato scelto per il file su disco, perché si copiano e incollano pulitamente attraverso ogni strumento di testo che gli umani possiedono, dai client di posta ai lettori PDF. La direzione inversa, leggere di nuovo un tale file in byte, è una chiamata nell'articolo correlato sulla decodifica; qui sei solo il mittente, e il lavoro del mittente è essere coerente.

La questione dell'archiviazione: file di configurazione, variabili d'ambiente e database

Gli sviluppatori amano mettere il Base64 in luoghi che accettano solo testo: file .env, impostazioni .ini, colonne TEXT. Il passaggio di codifica è banale, e la forma più comune è JSON dentro Base64:

import base64
import json
config = {"api_user": "svc-bot", "api_pass": "hunter2-not-really"}
packed = base64.b64encode(
  json.dumps(config).encode("utf-8")
).decode("ascii")
print(packed)
# eyJhcGlfdXNlciI6ICJzdmMtYm90IiwgImFwaV9wYXNzIjogImh1bnRlcjItbm90LXJlYWxseSJ9

E poi l'avvertimento, perché è qui che vive il malinteso più costoso di questo intero articolo. Base64 non è un offuscamento che regge, e non è crittografia. La sezione 12 di RFC 4648 lo dice in modo chiaro, quanto un RFC può: la codifica in base "nasconde visivamente informazioni altrimenti facilmente riconoscibili, come le password, ma non fornisce alcuna segretezza computazionale". Un file .env con segreti in Base64 ti protegge dalla persona che ci dà un'occhiata, non dalla persona che lo legge, e dopo un comando base64 -d il "segreto" sta lì nel loro terminale in testo normale. Se i dati sono davvero sensibili, cifrali prima - il pacchetto cryptography include Fernet esattamente per questo - e solo allora metti in Base64 il testo cifrato se la tua archiviazione richiede testo.

Un milione di byte dopo: big data e suddivisione in blocchi

b64encode è una funzione a velocità C - su un laptop tipico elabora un megabyte in circa un millisecondo - ma non è una funzione di streaming. Non esiste la coppia aggiungi-e-termina in nessun punto della libreria standard, quindi codificare dati più grandi di quelli che vuoi tenere in memoria significa fare l'aritmetica dei confini da solo. Tre byte in ingresso fanno quattro caratteri in output, quindi ogni confine di blocco deve cadere su un confine di tre byte:

import base64
def encode_chunks(chunks):
  out = []
  leftover = b""
  for chunk in chunks:
    buffer = leftover + chunk
    whole = len(buffer) // 3 * 3
    if whole:
      out.append(base64.b64encode(buffer[:whole]))
    leftover = buffer[whole:]
  if leftover:
    out.append(base64.b64encode(leftover))
  return b"".join(out)
with open("video.mp4", "rb") as handle:
  encoded = encode_chunks(iter(lambda: handle.read(65536), b""))

L'output è identico byte per byte a codificare l'intero file in una chiamata, perché il confine di tre byte è l'unico punto dove la raggruppatura può spezzarsi. Il riempimento appare esattamente una volta, sull'ultimo blocco, ed è quello che un decoder rigoroso dalla parte opposta si aspetta. La riga iter(lambda: handle.read(65536), b"") è l'idioma standard per leggere un file a pezzi di dimensione fissa, e la variabile leftover è l'intero algoritmo. Il lato decodifica tiene un confine di quattro caratteri invece di uno di tre byte, quindi i due articoli dividono l'aritmetica tra loro invece di ripeterla.

Dove i codificatori sbagliano

Il lato codifica ha meno trabocchetti del lato decodifica, perché c'è meno che possa andare storto quando sei tu a produrre le lettere. Detto questo, questi spuntano ogni settimana, e ognuno di loro ha una correzione di due minuti se lo riconosci presto:

  • Dai in pasto una stringa al codificatore. Il TypeError della sezione sul muro di tipi. Correggilo alla fonte con .encode("utf-8"), e pensa a quale codifica intendi davvero prima di digitarla.
  • Dimentichi che l'output è byte. b64encode restituisce byte; la str è quella che va in un URL o in un campo JSON, quindi il passaggio .decode("ascii") fa parte della ricetta, non è un dopo-pensiero.
  • Fai la sostituzione URL-safe a mano. str.replace("+", "-").replace("/", "_") funziona, ma è un debito di manutenzione di due lettere dove urlsafe_b64encode è una chiamata. Peggio, una sostituzione a metà, più corretti e barre dimenticate, produce un alfabeto che non corrisponde a nessuna specifica.
  • Lasci i riempimenti in un URL. Un = finale dentro una stringa di query viene codificato in percent da uno strumento e tolto da un altro, e l'aritmetica del riempimento del ricevente si rompe nel modo più confuso. Toglili; la lunghezza dice al decoder tutto quello che gli serve.
  • Avvolgi dove non è voluto. Gli a capo di encodebytes sono corretti per email e PEM, e veleno per un campo JSON o un URL. Un singolo a capo vagante basta a far lanciare un'eccezione a un decoder rigoroso dalla parte opposta, sui tuoi dati, non sul tuo formato.
  • Codifica doppia. I dati erano già Base64 a monte - un campo che arriva pre-codificato da un'altra API, un file che ha ricevuto il trattamento .b64 due volte - e il secondo passaggio produce una stringa che si decodifica di nuovo alla prima codifica. Fai l'andata-e-ritorno una volta, controlla i byte magici, e fermati.
  • Fidati del Base64 per i segreti. L'avvertimento della sezione sull'archiviazione, ripetuto perché costa soldi veri: se il modello di minacce include chiunque legga il file, ti serve una cifratura, non un alfabeto.

Un changelog che puoi davvero leggere

L'età del modulo si vede in miglioramenti silenziosi e datati, non in rivoluzioni. La versione corta, nell'ordine in cui i pezzi sono arrivati, dal punto di vista del codificatore:

Versione Cosa è successo
Python 2.4 (2004) Barry Warsaw porta il supporto completo a RFC 3548: le famiglie b16, b32 e b64, più le varianti standard_* e urlsafe_* usate oggi
Python 3.1 (2009) encodebytes arriva e encodestring viene deprecato, una rinomina che fa ancora inciampare i vecchi tutorial
Python 3.4 (2014) ogni codificatore accetta qualsiasi oggetto bytes-like, e a85encode e b85encode entrano nel modulo
Python 3.6 (2016) binascii.b2a_base64 impara un interruttore newline, che è ciò che lascia b64encode restare una riga unica infinita
Python 3.9 (2020) i nomi legacy encodestring e decodestring vengono infine rimossi
Python 3.10 (2021) b32hexencode e b32hexdecode, i cugini a alfabeto esadecimale ordinabile
Python 3.13 (2024) z85encode e z85decode, l'alfabeto di ZeroMQ, entrano nella famiglia
Python 3.14 (2025) import più veloci in tutta la libreria standard, base64 incluso, e un b16decode fino a sei volte più veloce, dato che la sua validazione ora corre su bytes.translate invece di un'espressione regolare

Il filo conduttore, se ne vuoi uno: il modulo è stato riscritto nel 1995 per delegare il suo lavoro al modulo binascii di livello C, e quella delega è ancora vera oggi. Il primo cambiamento dell'era dei byte, un commit del 2007 durante lo sviluppo di Python 3 che ha fatto usare byte dappertutto a tutto, è da dove viene il muro di tipi di questo articolo, ed è la ragione per cui un codificatore moderno prende byte e restituisce byte, con tutto il resto un involucro attorno a quel singolo contratto.

Cose che il modulo non ti dice

Il lavoro serio è finito, quindi ecco le piccole delizie sul lato codifica del registro:

  • L'esempio della documentazione stessa fa la stessa dimostrazione da oltre un decennio: entra b'data to be encoded', esce b'ZGF0YSB0byBiZSBlbmNvZGVk'. Hai incontrato questa coppia prima, che tu lo sappia o no.
  • La funzione C sotto b64encode aggiunge il suo a capo finale con un commento che recita "Aggiunge un a capo di cortesia". Un'intera cultura, in una riga di sorgente.
  • La parola password si codifica in cGFzc3dvcmQ=, ed è per questo che il Base64 in un file di log sembra un segreto a uno scanner e a un comando dall'esserlo per un lettore.
  • b64encode non avvolge mai. Mai. Un gigabyte di input produce una singola riga da 1,3 gigabyte, e la funzione non batte ciglio. Se volevi righe, dovevi chiedere encodebytes.
  • La docstring del modulo cita ancora RFC 3548, l'edizione 2003 della specifica. RFC 4648 ha preso il sopravvento nel 2006; la docstring semplicemente non se ne è mai accorta.
  • Python 2 non aveva un muro di tipi del tutto: b64encode accettava di buon grado una str e ne restituiva una. Il ribaltone sui byte del 2007 ha chiuso tutto, e sono i vecchi tutorial Python 2 a cui la maggior parte dei thread "perché la mia codifica crasha" ancora puntano.
  • z85encode, il membro più nuovo della famiglia (Python 3.13), è il più pretenzioso: ZeroMQ incornicia i dati in gruppi di quattro byte, quindi la specifica richiede che l'output codificato sia un multiplo di cinque caratteri - e la documentazione mette il riempimento a te: l'input deve arrivare multiplo di 4 byte (il codificatore non lo riempie per te; un input di 3 byte produce un frame di 4 caratteri che nessun pari ZeroMQ accetterà).

Quindi la filosofia del codificatore in tre regole. Decidi i byte prima e la codifica dopo, perché il muro di tipi è dove nascono la maggior parte dei bug Base64 di Python. Scegli l'alfabeto per il canale, non per i dati: standard con riempimenti per email e file, base64url senza riempimenti per URL e token, e non improvvisare mai una terza variante alla tastiera. E tieni l'output nella forma che il suo lettore si aspetta, una riga per JSON e header, righe da 76 caratteri per MIME e PEM, perché il decoder dalla parte opposta non ammetterà eccezioni.

Quando quelle lettere arrivano dall'altra parte, il divertimento comincia davvero: riempimenti mancanti, scarti silenziosi, carichi utili che non sono proprio Base64, e un decoder con due umori da navigare. Tutto questo è coperto in dettaglio nell'articolo correlato sulla decodifica Base64 in fondo a questa pagina, e le due guide si leggono bene come coppia. Buona codifica.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in Python: una guida completa