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.

Decodifica Base64 in Swift: una guida completa

Da qualche parte nella tua pipeline, i dati indossano un travestimento: un token riposto in un header HTTP, un avatar nascosto in un campo JSON, un file .b64 che hai promesso di guardare la settimana scorsa, un allegato email arrivato come un muro di lettere. Togliere questi travestimenti in Swift è uno dei lavori più gradevoli della lingua: un solo framework, un solo costruttore, e un libro delle regole così corto da stare su un post-it.

La home page di questo sito tratta già il formato in sé (64 caratteri stampabili, sei bit per carattere, al massimo due caratteri = di padding nell'ultimo gruppo), quindi non la racconteremo di nuovo qui. Tieni solo due fatti in tasca. Primo: base64 è un modo per travestire i byte come testo, non un lucchetto. Secondo: ogni viaggio base64 in Swift passa attraverso un unico tipo, Data, e il decodificatore ci vive sopra come costruttore fallibile. Quel singolo fatto plasma il resto di questo articolo, perché un costruttore fallibile cambia il modo in cui scrivi ogni riga che viene dopo.

Un solo tipo gestisce tutto il lavoro

Swift non sparpaglia gli strumenti base64 in una dozzina di moduli, e non ti fa installare nulla. Il decodificatore è Data(base64Encoded:options:) in Foundation, e fa parte della piattaforma fin dai primi giorni del framework (Apple riporta il costruttore a partire da iOS 8.0, macOS 10.10, tvOS 9.0, watchOS 2.0 e visionOS 1.0; le opzioni di lunghezza della riga sul lato codifica risalgono addirittura a iOS 7.0). Su Linux e Windows lo stesso Foundation è incluso nella toolchain open-source, quindi il codice qui sotto si comporta allo stesso modo in un'app per iPhone, in un worker di server e in uno script del tuo terminale.

C'è un costruttore fratello, Data(base64Encoded: Data, options:), per il caso in cui il tuo base64 arrivi come byte ASCII grezzi invece che come stringa. Entrambi accettano un argomento options che di default vale []. E entrambi condividono un tratto di personalità che conta più di qualsiasi opzione: sono fallibili.

import Foundation

let packed = "SGVsbG8sIFN3aWZ0IQ=="
if let data = Data(base64Encoded: packed) {
  let text = String(data: data, encoding: .utf8)
  print(text ?? "not text after all")
} else {
  print("that was not base64")
}
// Hello, Swift!

La documentazione di Apple per il costruttore è splendidamente diretta: "restituisce nil quando l'input non viene riconosciuto come Base-64 valido". Nessuna eccezione, nessun errore lanciato, nessun spam di log. Solo una tranquilla nil e la responsabilità di decidere cosa significhi per il tuo utente. Se devi ricordare una sola cosa sul base64 in Swift, che sia questa: il decodificatore non va mai in crash e non si lamenta mai. Si limita a rifiutare.

Il verdetto del decodificatore: una tabella di sì e no

Allora, cosa significa "valido" per questo decodificatore? Si rivela essere una lista corta di regole rigide, ed è proprio quella lista la differenza tra "funziona nella demo" e "sopravvive in produzione". Ogni riga della tabella qui sotto è comportamento reale del costruttore su una toolchain attuale, quindi puoi citarla direttamente nei tuoi messaggi di errore:

Input Verdetto Motivo
TWFu Man un gruppo completo di quattro caratteri non ha bisogno di nessun padding
TQ== M un byte più due pad, il caso da manuale
SGVsbG8h Hello! otto caratteri sono un multiplo di quattro, quindi non servono pad
==== un Data vuoto un padding senza nulla dietro è legale e si decodifica in zero byte
la stringa vuota un Data vuoto niente in ingresso, niente in uscita, e l'optional ha comunque successo
TQ nil lunghezza due: un gruppo di quattro era promesso ma non è mai arrivato
T nil un carattere porta sei bit e un byte ne richiede otto
SGVsbG8hTQ nil dieci caratteri: l'ultimo gruppo pende senza i suoi pad
TQ=== nil tre pad: il terzo non ha più nulla da riempire
TQ==TQ nil dati dopo il padding: un no secco
SGVs bG8h nil uno spazio singolo è fuori alfabeto, e la modalità rigorosa non fa sconti
SGVsbG8h più un a capo finale nil l'a capo in fondo a un file che hai appena letto conta come rumore

Tre righe meritano un secondo sguardo. La riga ==== significa che il controllo if let passa e il tuo codice va dritto per la sua strada con zero byte, quindi se un payload vuoto non è uno stato valido nella tua app, controlla il conteggio subito dopo la decodifica. La riga della stringa vuota è lo stesso trucco, con meno trucco. E la riga dell'a capo finale è la ragione più comune in assoluto per cui un file base64 che si era codificato perfettamente al mattino rifiuta di decodificarsi al pomeriggio: qualcosa lungo il percorso ha aggiunto un terminatore di riga, e il decodificatore rigoroso se la prende sul personale.

C'è anche un punto debole famoso che la tabella non può mostrare. Confronta TQ== e TS==: entrambi si decodificano nello stesso byte, M, perché i due bit meno significativi di quel carattere finale vengono scartati prima ancora di essere ispezionati. Usa al suo posto Tg== e ottieni N senza proteste. Il decodificatore fa la polizia dei caratteri e lascia correre i bit finali. Questa tolleranza non è un bug, ma significa che due stringhe diverse possono rappresentare gli stessi dati, e inizia a contare nel momento in cui il tuo sistema confronta, deduplica o mette in cache valori base64 (ne parleremo nella sezione sicurezza).

Quando l'input è più rumoroso di quanto pensi

Il base64 del mondo reale raramente arriva come una riga immacolata. Gli allegati email sono spezzati a 76 caratteri con un carriage return e un line feed dopo ogni riga, un'abitudine ereditata dalla specifica MIME del 1996, e i file di certificati sono spezzati a 64. Il decodificatore ha esattamente un'opzione per gestire quel rumore, e non è poco:

import Foundation

let mimeBody = "SGVs\r\nbG8sIG1h\naWwgbm9pc2Uu"
if let data = Data(base64Encoded: mimeBody, options: .ignoreUnknownCharacters) {
  print(String(data: data, encoding: .utf8) ?? "")
}
// Hello, mail noise.

.ignoreUnknownCharacters è documentato come un decodificatore che "ignora i byte non Base-64 sconosciuti, inclusi i caratteri di terminazione di riga", e per quel lavoro è lo strumento giusto: il rumore viene eliminato, l'alfabeto sopravvive, e il payload ne esce intero. Ma l'opzione ha un punto cieco, ed è quello che morde più duro gli sviluppatori Swift: elimina ogni carattere fuori alfabeto, inclusi - e _ del base64url. Non li traduce in + e /; se ne sbarazza. A seconda di cosa lascia indietro quell'eliminazione, ottieni una nil (quando i superstiti non formano più gruppi interi) o, peggio, una risposta sicura con il numero sbagliato di byte. Una stringa base64url di 16 caratteri che codifica 12 byte può tornare dal decodificatore tollerante come 9 byte diversi, senza errori e senza scuse.

La regola da tenere: .ignoreUnknownCharacters è per il rumore di trasporto (a capo, spazi vaganti da un copia-incolla), mai per le differenze di alfabeto. Se il payload potrebbe essere base64url, converti prima i caratteri tu, esattamente come mostra la prossima sezione, e passa al decodificatore una stringa standard pulita.

L'alfabeto per URL

La sezione 5 di RFC 4648 definisce il cugino dell'alfabeto standard che hai incontrato finora: base64url, dove + diventa -, / diventa _, e il padding = viene di solito tolto. La ragione è la stessa che tiene i tuoi URL onesti: in una query string, un + viene letto come spazio dal parsing dei form, un / è un separatore di percorso, e un = separa le chiavi dai valori. Lo RFC è diretto sulla relazione tra i due: la variante URL "non deve essere considerata la stessa della codifica base64". JWT, messaggi Web Push, ID video di YouTube e la maggior parte degli identificatori API moderni parlano base64url, quindi aspettati di incontrarlo già il primo giorno.

Sul lato decodifica, la ricetta ha due mosse: traduci l'alfabeto, poi completa il padding, perché il decodificatore rigoroso vuole sempre il suo multiplo di quattro.

import Foundation

extension String {
  func dataFromBase64URL() -> Data? {
    var fixed = self
      .replacingOccurrences(of: "-", with: "+")
      .replacingOccurrences(of: "_", with: "/")
    let missing = fixed.count % 4
    if missing > 0 {
      fixed += String(repeating: "=", count: 4 - missing)
    }
    return Data(base64Encoded: fixed)
  }
}

let tokenPart = "0S__zMWaTC-iVgJ-"
if let bytes = tokenPart.dataFromBase64URL() {
  print(bytes.count) // 12
}

La riga del modulo è tutto il trucco: i payload base64url arrivano tipicamente senza padding, e uno o due caratteri = (mai tre) ripristinano il gruppo di quattro che il decodificatore si aspetta. Troverai una versione di questa estensione di cinque righe in un numero sorprendente di codebase Swift, e per buona ragione. C'è un motivo per cui sarà più corta in futuro: gli SDK Apple più recenti (26.4 in su, a data di stesura) hanno fatto crescere un'opzione nativa .base64URLAlphabet per il codificatore, con le opzioni di decodifica corrispondenti ancora in maturazione nella Foundation open-source, dietro un marcatore di disponibilità per una toolchain successiva. Finché non arriverà al tuo target di deployment minimo, l'estensione è la risposta portatile, e continuerà a funzionare su ogni versione per costruzione.

Prima i byte, poi le parole

Ecco la decisione che il decodificatore non può prendere per te: ti passa un Data, un sacco di byte senza la minima idea di in quale set di caratteri fosse scritto il payload originale. Se il payload era testo, scegliere quel set di caratteri è compito tuo, e Swift ti offre due porte d'uscita dal mondo dei byte, con temperamenti molto diversi.

  • String(data:encoding:) è la porta rigorosa. Restituisce un optional e risponde nil quando i byte non sono validi nella codifica che hai indicato. Ideale per la validazione, pericoloso se fai il force-unwrap della risposta.
  • String(decoding:as:) è la porta che non rifiuta mai. Restituisce sempre una stringa, al posto di tutto ciò che non riesce a interpretare inserendo il carattere di sostituzione U+FFFD. Ideale per log e anteprime, pericoloso se salvi il risultato e lo chiami dati.
import Foundation

let bytes = Data([0xC3, 0xA5]) // la grafia UTF-8 della lettera a con cerchietto
print(String(data: bytes, encoding: .utf8) ?? "?")      // a con cerchietto, letta correttamente
print(String(data: bytes, encoding: .isoLatin1) ?? "?") // due lettere confuse, stessi byte
print(String(decoding: bytes, as: UTF8.self))           // a con cerchietto, e non va mai in crash

La ricetta che copre quasi tutto: prova prima l'UTF-8 rigoroso, perché è quello che le API moderne quasi sempre intendono; ricorri all'ISO Latin-1 solo quando il contratto tace e preferisci il leggibile-ma-sbagliato al silenzio; riserva la porta che non rifiuta mai all'output di debug. E un intruso invisibile da controllare: se il payload inizia con un BOM UTF-8 (i tre byte EF BB BF), la conversione rigorosa lo mantiene, e la tua stringa inizia ora con un carattere U+FEFF invisibile che rompe in silenzio i controlli di uguaglianza e i round trip JSON. Rimuovilo con un controllo sul prefisso quando la specifica non promette che c'è.

Aprire i file

Il lavoro del tipo "c'è un file .b64, dammi quello che nasconde" è una lettura, un trim, una decodifica e una scrittura. Il trim non è decorazione: è la differenza tra un file che si apre e un file che restituisce nil, perché strumenti, client email ed editor adorano tutti lasciare un a capo alla fine:

import Foundation

let inbox = URL(fileURLWithPath: "Downloads/avatar.b64")
let outbox = URL(fileURLWithPath: "Downloads/avatar.png")
let raw = try String(contentsOf: inbox, encoding: .utf8)
if let data = Data(base64Encoded:
  raw.trimmingCharacters(in: .whitespacesAndNewlines)) {
  try data.write(to: outbox)
} else {
  print("the file was not base64 after all")
}

Se il file è avvolto in stile MIME (a capo ogni 76 caratteri), hai due vie d'uscita pulite: decodifica con .ignoreUnknownCharacters e lascia che l'opzione si mangi gli a capo, oppure toglili tu con replacingOccurrences prima di una decodifica rigorosa. Bastano una riga ciascuno. Per i file che sono semplicemente grandi, decodifica a gruppi allineati invece di leggere tutto intero: ogni gruppo di quattro caratteri si decodifica da solo, quindi puoi portare oltre i confini di lettura solo il gruppo corrente più un piccolo resto.

import Foundation

func decodeBase64Chunks(_ stream: InputStream, into result: inout Data) throws {
  let chunkSize = 65_536
  var buffer = [UInt8](repeating: 0, count: chunkSize)
  var leftover = ""
  result = Data()
  stream.open()
  defer { stream.close() }
  while stream.hasBytesAvailable {
    let read = stream.read(&buffer, maxLength: chunkSize)
    if read < 0 { throw CocoaError(.fileReadUnknown) }
    if read == 0 { break }
    var text = String(decoding: buffer[0..<read], as: UTF8.self)
    text = text.replacingOccurrences(of: "\r", with: "")
      .replacingOccurrences(of: "\n", with: "")
    text = leftover + text
    if text.count % 4 != 0 {
      let whole = text.count - (text.count % 4)
      leftover = String(text.suffix(text.count - whole))
      text = String(text.prefix(whole))
    } else {
      leftover = ""
    }
    guard !text.isEmpty else { continue }
    guard let part = Data(base64Encoded: text) else {
      throw CocoaError(.fileReadCorruptFile)
    }
    result.append(part)
  }
  if !leftover.isEmpty {
    guard let part = Data(base64Encoded: leftover) else {
      throw CocoaError(.fileReadCorruptFile)
    }
    result.append(part)
  }
}

La memoria resta piatta non importa quanto sia grande il file: un buffer di lettura, un frammento residuo e il risultato che stai costruendo. Lo stesso loop gestisce un download che arriva via rete come base64, un file di log che è in realtà uno stream codificato, o un payload troppo grande per tenerlo in mano.

JWT: leggere i tre puntini

Un JSON Web Token compatto è fatto di tre parti base64url unite da puntini, e le prime due di quelle parti sono JSON comune con un trench addosso. Arrivano senza padding, che è esattamente la combinazione che il decodificatore rigoroso rifiuta a prima vista, quindi il tuo helper dataFromBase64URL() della sezione URL fa tutto il lavoro pesante:

import Foundation

let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

func openPart(_ part: String) -> String? {
  var fixed = part
    .replacingOccurrences(of: "-", with: "+")
    .replacingOccurrences(of: "_", with: "/")
  let missing = fixed.count % 4
  if missing > 0 {
    fixed += String(repeating: "=", count: 4 - missing)
  }
  guard let data = Data(base64Encoded: fixed) else { return nil }
  return String(data: data, encoding: .utf8)
}

let pieces = token.split(separator: ".")
print(openPart(String(pieces[0])) ?? "?")
// {"alg":"HS256","typ":"JWT"}
print(openPart(String(pieces[1])) ?? "?")
// {"sub":"1234567890","name":"John Doe"}

Due promemoria viaggiano insieme. Un JWT è firmato, non cifrato: l'header e il payload sono informazioni pubbliche, ed è esattamente per questo che una password non deve mai finirvi dentro (il cugino cifrato, JWE, è una specifica a sé). E la terza parte separata da puntini è una firma crittografica, non un documento, quindi decodifica le parti uno e due e lascia stare il resto.

Data URI: il file dietro la virgola

Le API web adorano nascondere il binario nel testo con lo scheme data:: un PNG in un campo del profilo, un font in un blob CSS, un codice QR in un file di configurazione. Il formato è data:{mime};base64,{payload}, e staccare il payload è questione di un solo split:

import Foundation

let uri = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
let payload = uri.components(separatedBy: ",").last ?? ""
if let bytes = Data(base64Encoded: payload) {
  print(String(decoding: bytes.prefix(6), as: UTF8.self)) // GIF89a
  print(bytes.count)                                      // 42
} else {
  print("not a base64 data uri")
}

L'esempio usa il famoso GIF trasparente di 42 byte, l'immagine più piccola del formato, ed è per questo che i suoi caratteri iniziali compaiono in più codebase di quasi ogni altra stringa base64 su internet. Sulle piattaforme Apple la pipeline si chiude in una riga sola: lo stesso Data che hai appena decodificato va dritto in UIImage(data:) o NSImage(data:), ed è per questo che "mostrare un avatar da un'API" è una piccola feature e non un progetto.

HTTP: l'header Basic e i suoi amici

L'header Authorization: Basic classico è un nome utente e una password, uniti da due punti e impacchettati con base64 standard per il viaggio (non il dialetto URL: questo vive in un header dove + e / sono perfettamente inoffensivi). Aprirlo è questione di uno split e una decodifica:

import Foundation

let header = "Basic ZWRpdG9yOnMzY3JldA=="
let packed = header.replacingOccurrences(of: "Basic ", with: "")
if let creds = Data(base64Encoded: packed) {
  print(String(data: creds, encoding: .utf8) ?? "") // editor:s3cret
} else {
  print("malformed header")
}

Tieni alta la nota a piè di pagina sulla sicurezza, perché vale per ogni base64 che incontrerai mai: questo è impacchettamento, non protezione. L'autenticazione Basic è accettabile solo su HTTPS, dove è TLS a fare la guardia vera e il base64 si limita a impedire ai byte di rompere la grammatica dell'header. Lo stesso ragionamento spiega i token Authorization: Bearer: il token in sé è un JWT, quindi la ricetta di decodifica della sezione JWT gli si applica senza modifiche.

Email: l'abitudine dei 76 caratteri

Un allegato email codificato in base64 è spezzato a 76 caratteri con a capo CRLF, esattamente il rumore per cui esiste l'opzione tollerante. Gli header MIME grezzi ti dicono quale alfabeto e quale spezzatura ha usato il mittente (Content-Transfer-Encoding: base64), e la soluzione è un singolo flag:

import Foundation

let attachment = "VGhpcyBhdHRhY2htZW50IHN1cnZpdmVk\r\nIHRoZSA3Ni1jaGFyYWN0ZXIgaGFiaXQu"
if let data = Data(base64Encoded: attachment, options: .ignoreUnknownCharacters) {
  print(String(data: data, encoding: .utf8) ?? "")
}
// Questo allegato è sopravvissuto all'abitudine dei 76 caratteri.

Se stai scrivendo una feature di posta invece di leggerla, ricorda che la spezzatura a 76 caratteri costa anche a te: con un a capo ogni 76 caratteri, il testo codificato atterra vicino al 137 percento della dimensione originale, ed è per questo che i vecchi ingegneri della posta stimavano la dimensione degli allegati a occhio, con la scorciatoia "moltiplica l'originale per 1.37, aggiungi circa 800 byte di header". Oggi quel numero è folklore, ma l'aritmetica è sempre l'aritmetica.

Il contenuto con doppio imballaggio

Il ticket più comune del tipo "i miei dati sono corrotti" nel mondo del base64 è dato che è stato impacchettato due volte: un livello di integrazione lo ha codificato, e un secondo livello che non ha mai letto la documentazione ha codificato il risultato. La mossa difensiva è decodificare una volta, guardare cosa ti è arrivato, e se il risultato è a sua volta una stringa pulita dall'aria base64 (lunghezza giusta, alfabeto giusto, nulla di strano), decodificarlo ancora una volta, con intenzione, e fermarsi. Non scrivere un loop che decodifica finché non fallisce. Un loop così si mangia volentieri un file perfettamente buono il cui contenuto per caso ha un'aria base64, e dopo che ha girato, nessuno riesce a dire dove iniziano i dati originali.

import Foundation

func unwrapOnce(_ packed: String) -> Data? {
  let cleaned = packed.trimmingCharacters(in: .whitespacesAndNewlines)
  return Data(base64Encoded: cleaned)
}

let suspicious = "WVdKag==" // ha già l'aria di essere stato impacchettato
if let first = unwrapOnce(suspicious) {
  let inner = String(data: first, encoding: .utf8) ?? ""
  if let second = unwrapOnce(inner) {
    print("it was wrapped twice:", String(data: second, encoding: .utf8) ?? "?")
  }
}
// it was wrapped twice: abc

Due estrazioni, due decisioni consapevoli, e un payload che è di nuovo solo abc.

Dare un significato alla nil

Poiché il decodificatore risponde nil invece di lanciare, lo stile di gestione degli errori del tuo codice base64 è una scelta che fai tu, e la scelta di cui ti ringrazierai in seguito è un piccolo wrapper che trasforma il rifiuto silenzioso in un errore forte e specifico:

import Foundation

enum Base64Failure: Error, CustomStringConvertible {
  case notBase64(Int)

  var description: String {
    switch self {
    case .notBase64(let length):
      return "input of \(length) characters is not valid base64"
    }
  }
}

func decodeStrict(_ text: String) throws -> Data {
  let cleaned = text.trimmingCharacters(in: .whitespacesAndNewlines)
  guard let data = Data(base64Encoded: cleaned) else {
    throw Base64Failure.notBase64(cleaned.count)
  }
  return data
}

do {
  let bytes = try decodeStrict("c3ludGF4IGVycm")
  print(String(data: bytes, encoding: .utf8) ?? "?")
} catch {
  print(error) // input of 14 characters is not valid base64
}

Il wrapper diventa anche l'unico posto dove vive la normalizzazione: il trim, ogni traduzione di alfabeto, ogni completamento del padding. Chi chiama riceve una funzione, un solo significato per il fallimento, e nessun ! in vista. Fare il force-unwrap di Data(base64Encoded:)! è il modo in cui un payload malato diventa un'app in crash, e il wrapper è l'assicurazione economica contro questo. Lo stesso schema funziona in riga di comando, dove uno script con CommandLine.arguments e una scrittura su FileHandle trasforma "decodifica questo file dalla shell" in una utility di cinque righe invece che in una deviazione di copia-incolla attraverso un sito web.

Sicurezza, misurata in byte

  • Non è cifratura. Il base64 è un ri-imballaggio reversibile, leggibile all'istante. Se il tuo modello di minaccia include un umano con un browser e cinque secondi, hai zero protezione, e ogni header JWT lo dimostra ogni giorno.
  • Metti in forma canonica prima di confrontare. Dato che TQ== e TS== si decodificano negli stessi byte, due sistemi possono conservare grafie diverse degli stessi dati. Un paper del 2022, "La malleabilità del Base64 in pratica", ha documentato cosa fa nel mondo reale quella garanzia di unicità rotta: discrepanze nei log, attacchi denial-of-service e voci duplicate nel database. Se la tua app Swift mette in cache, deduplica o confronta valori base64, esegui una decodifica in forma canonica (o una ricodifica in forma canonica) al varco.
  • Poni un limite all'input prima di decodificare. Decodificare N caratteri alloca grosso modo tre quarti di N byte mentre hai ancora in mano la stringa di input. Un client ostile può inviarti 100 megabyte di lettere A e guardare la tua memoria salire prima che il decodificatore si decida a dire di no. Controlla prima la lunghezza, a basso costo, e rifiuta ciò che è troppo grande.
  • Diffida dell'opzione tollerante usata come filtro. .ignoreUnknownCharacters elimina caratteri. Un passaggio "sanificante" attraverso di essa può trasformare un payload base64url valido in dati diversi, senza errori. È un filtro di rumore per gli a capo, non un validatore.
  • Tienilo fuori dagli URL quando puoi. Payload base64 grandi in query string o path sfondano le lunghezze comode degli URL e vengono sfigurati dai proxy. Mettili nei body delle richieste, nei file o nei token.

Prestazioni, in breve

Il decodificatore è una passeggiata su una tabella di ricerca: ogni carattere viene indicizzato in una piccola tabella e pochi bit vengono spostati e uniti tramite OR nei byte di output. Su una toolchain attuale è abbastanza veloce per tutto ciò che sta in memoria, e il numero da ricordare è il rapporto di output: i byte decodificati sono circa tre quarti della lunghezza dell'input, quindi una stringa da 4 megabyte ti costa intorno ai 3 megabyte di risultato in più sulla stringa che hai già in mano. Se sei su un percorso dove la Foundation stessa non è ammessa (un target profondamente embedded, un bundle WebAssembly), il pacchetto della community swift-extras-base64 è l'alternativa degna di nota: Swift puro senza dipendenza da Foundation, un codificatore e un decodificatore conformi a RFC 4648 con opzioni base64url e padding, e benchmark che lo mettono a diverse volte più veloce della Foundation. Un'implementazione precedente dello stesso pacchetto è inclusa persino nel supporto WebSocket di swift-nio, che è quanto un side project possa avvicinarsi al livello di produzione. Per un'app o uno script ordinari è bagaglio inutile; per l'angolo vincolato di Swift è la risposta standard.

Una decina d'anni di estrazione

Swift non ha inventato nulla di tutto questo, e vale la pena sapere da dove arriva ogni pezzo della cassetta degli attrezzi:

  • Anni '80, l'era della stessa macchina. Le prime codifiche di questa famiglia (uuencode su UNIX, BinHex sul TRS-80 e sul Mac classico) spostavano file tra macchine che assumevano che l'altro capo fosse come la loro. uuencode usava un alfabeto di lettere maiuscole, cifre e punteggiatura, e le sue lettere stanno in posizioni ASCII consecutive, quindi codificare era questione di aggiungere 32, senza tabella di ricerca di alcun tipo. I decodificatori di quell'epoca potevano dare molto per scontato, e nel momento in cui i dati attraversavano ecosistemi, cadevano a pezzi.
  • 1987, l'alfabeto riceve un indirizzo. RFC 989 (Privacy-Enhanced Mail, febbraio 1987) ha standardizzato l'alfabeto di 64 caratteri, ha spezzato le righe a esattamente 64 caratteri, e ha usato = per il padding e * per marcare i dati codificati ma non cifrati. Ogni blocco in stile PEM è un discendente di quel documento.
  • 1996, l'era liberale. MIME (RFC 2045) prese l'alfabeto per l'email, spostò la spezzatura a 76 caratteri, e disse ai decodificatori conformi di ignorare ogni carattere fuori dall'alfabeto, come gli a capo CRLF. È l'era che ha addestrato una generazione ad aspettarsi decodificatori tolleranti, ed è l'era le cui aspettative il default rigoroso di Swift rompe deliberatamente.
  • 2003-2006, le regole si induriscono. RFC 3548 (2003) fece il primo tentativo di unificare la famiglia; RFC 4648 (ottobre 2006) la chiuse, codificò le regole del padding e aggiunse l'alfabeto URL-safe. Il suo paragrafo sui decodificatori è quello che Swift segue: rifiutare i caratteri fuori dall'alfabeto, a meno che il formato che stai gestendo non dica esplicitamente di ignorarli, come fa MIME.
  • 2013-2014, l'API aspetta dietro le quinte. La classe NSData di Apple impacchettava e apriva base64 da anni, e l'API basata su opzioni con la sua opzione di decodifica è atterrata in iOS 7, nel 2013, un anno prima che Swift esistesse. Quando Swift 1.0 è uscito il 9 settembre 2014, il decodificatore è entrato in scena con la lingua e da allora ha mantenuto la stessa personalità: nucleo rigoroso, una manopola tollerante, costruttore fallibile.
  • 3 dicembre 2015, Linux riceve un decodificatore. Swift è diventato open-source quel giorno, e con lui il base64 della Foundation è passato su Linux e, più tardi, su Windows. "Decodifica base64 in Swift su una macchina non Apple" ha appena una decina d'anni: un ospite in ritardo a una festa iniziata nel 1987.
  • 2023-2026, la riscrittura. La riscrittura della Foundation (il progetto swift-foundation) ha spostato Data in un nucleo Swift puro, e nel 2025 una proposta della community ha aggiunto opzioni native base64url e di omissione del padding. A data di stesura gli SDK beta più recenti e la toolchain open-source distribuiscono le opzioni di codifica, mentre le opzioni di decodifica sono ancora in maturazione nella toolchain open-source, quindi nel frattempo le estensioni fatte a mano restano la risposta universale.

Piccoli miracoli

  • ==== è un input legittimo. Quattro pad e nessun dato si decodificano in un Data vuoto, l'unica stringa base64 il cui contenuto intero è "non c'è niente qui", e Swift è d'accordo.
  • La polizia dei caratteri del decodificatore non controlla il lavoro della polizia dei bit: TS== e TQ== ti consegnano entrambi M, mentre Tg== ti consegna N. Stessa grammatica, bit diversi, nessuna domanda.
  • Il Data di Swift può decodificare base64 arrivato come byte invece che come stringa, attraverso la variante Data(base64Encoded: Data), quindi un payload che ha attraversato la rete in ASCII può saltare del tutto l'andata e ritorno della stringa.
  • La parola che è base64 fin da quando sono nati i vettori di test è foobar, e si impacchetta in Zm9vYmFy. Se hai mai visto un esempio di base64 nel mondo reale, c'è una buona probabilità che foobar ci fosse.
  • Il famoso GIF trasparente 1x1 è esattamente 42 byte e si apre con la parola magica GIF89a, ed è per questo che i suoi primi otto caratteri codificati compaiono in più codebase di quasi ogni altro prefisso base64 sulla Terra.
  • Il decodificatore open-source moderno fa il suo controllo dei caratteri non validi con un singolo confronto: fa l'or di quattro valori di ricerca per posizione e confronta il risultato con un valore sentinella, così un solo ramo decide il destino di un intero gruppo di quattro caratteri. L'implementazione più vecchia faceva lo stesso lavoro con una tabella di 128 byte in cui ogni valore a 0x80 o oltre significava "non è una lettera".
  • I BOM UTF-8 sono invisibili: EF BB BF all'inizio di un payload diventa un carattere U+FEFF che sopravvive alla conversione rigorosa e poi rompe i controlli di uguaglianza qualche riga di codice dopo.
  • Swift ha 27 anni in meno dell'alfabeto che decodifica. La lingua è uscita nel 2014; le 64 lettere che gestisce sono state standardizzate nel 1987 e da allora non sono più cambiate.

Questa è l'intera cassetta degli attrezzi di decodifica: un costruttore fallibile con un libro delle regole corto, una manopola tollerante con un punto cieco documentato, un helper base64url di cinque righe, una decisione sul set di caratteri che spetta a te, un loop a blocchi per i file grandi, e un wrapper che dà un significato alla nil. La decodifica è il punto dove il base64 morde, e ora conosci il nome di ogni dente. Quando il lavoro si ribalta e inizi a impacchettare i byte per il viaggio invece di aprirli, il sovrapprezzo di circa il 33 percento prende il sopravvento e compaiono le opzioni di spezzatura. L'articolo collegato sulla codifica copre per intero quella metà dell'andata e ritorno, quindi vai là quando sarai pronto a spedire nella direzione opposta.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Codifica Base64 in Swift: una guida completa