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

Hai dei byte che devono viaggiare, e la strada permette solo il testo. Una JPEG che deve vivere dentro un campo JSON. Un certificato che deve stare in una variabile d'ambiente. Un grafico che deve viaggiare dentro un report HTML autosufficiente. Il Base64 è il nastro imballaggio per tutto: qualsiasi sequenza di byte diventa una stringa di 64 caratteri inoffensivi che sopravvive a qualsiasi canale di testo. La home page di questo sito copre il formato per intero, quindi ecco la versione corta: tre byte entrano, quattro caratteri escono, tratti da A a Z, a a z, 0 a 9, più + e /, con un po' di padding = in coda quando il carico non è divisibile esattamente.

La svolta specifica di R: il R di base non include nessun encoder Base64. Non c'è un base64_encode() in attesa in un pacchetto base, e non c'è una funzione integrata di una riga a cui ricorrere. Scegli un pacchetto, e l'ecosistema ti dà davvero una scelta, con velocità diverse, abitudini di a capo diverse, e opinioni diverse sul padding. A fine articolo saprai quale encoder usare in ogni situazione, e quale di loro farà in silenzio qualcosa di diverso dal codificare.

Il panorama degli encoder

Cinque pacchetti fanno la codifica, e si dividono nei cavalli da lavoro di tutti i giorni, in quelli vicini alla crittografia e nei piccoli specialisti. Ecco il cast, aggiornato al 2026:

Pacchetto Versione (2026) Punti d'ingresso per la codifica Abitudini di a capo Usalo quando
base64enc 0.1-6 base64encode() linewidth e newline, del tutto tuoi da impostare stringhe di tutti i giorni, a capo MIME
openssl 2.4.2 base64_encode() righe da 64 caratteri, a capo LF, a capo finale file PEM, stack di crittografia esistenti
b64 0.1.7 encode(), encode_file() non va mai a capo; b64_chunk() e b64_wrap() su richiesta velocità, vettori, motori sicuri per URL
base64 2.0.2 encode() righe da 64 caratteri più un a capo finale, di default faccende da file a file, immagini di report
base64url 1.4 base64_urlencode() non va mai a capo, senza padding, caratteri in ingresso stringhe sicure per URL

Altri tre encoder si nascondono dentro pacchetti che potresti già caricare. jsonlite esporta base64_enc() e base64url_enc(), quindi se già fai il parsing di JSON potresti già avere un encoder a portata di mano. jose esporta base64url_encode() per il lavoro con i JWT. E il veterano pacchetto RCurl porta ancora un base64() wrapper attorno a libcurl che funziona bene e appartiene a un'era precedente. Il pacchetto base64, infine, ora si descrive sulla scatoletta come un wrapper di compatibilità e indirizza le nuove applicazioni a base64enc, openssl o jsonlite.

Preparativi

Se R non è ancora sulla macchina, il tuo sistema operativo lo distribuisce: r-base su Debian e Ubuntu, R su Fedora, Homebrew o l'installer ufficiale su macOS, un installer su Windows. Poi gli encoder, direttamente da CRAN:

install.packages("base64enc")
install.packages("openssl")
install.packages("b64")
install.packages("base64url")

I due punti in cui le installazioni vanno storto sono entrambi in fase di compilazione. openssl viene compilato contro il tuo OpenSSL di sistema, quindi una macchina Linux nuda vuole prima gli header di sviluppo (sudo apt install libssl-dev), oppure puoi saltare del tutto la compilazione su Debian e Ubuntu con sudo apt install r-cran-openssl. E b64 è un motore Rust avvolto con extendr, quindi una compilazione da fonte vuole la toolchain Rust (sudo apt install cargo porta con sé anche rustc). Windows e macOS ottengono binari precompilati da CRAN e nessuna di queste cose si applica.

La prima codifica

Novanta per cento della vita di codificazione sta in tre righe, usando la stessa famosa stringa che il lato decodifica usa come smoke test:

library(base64enc)
packed <- base64encode(charToRaw("Man"))
packed
#> [1] "TWFu"
identical(packed, "TWFu")
#> [1] TRUE

In quella cerimonia, tre cose da notare. Prima, l'input è un vettore raw: charToRaw() è il ponte dalla tua stringa R ai byte che vengono impacchettati, e gli encoder di tutti i giorni di questa tabella - base64enc, openssl, b64 - prendono tutti raw. Secondo, l'output è la direzione opposta rispetto ai decoder: una singola stringa di caratteri, perché la codifica finisce sul lato testo del confine. Terzo, guarda la matematica delle lunghezze in azione: tre byte in ingresso, quattro caratteri in uscita, nessun padding necessario perché il carico è divisibile esattamente. Quando il carico non è divisibile, uno o due caratteri = atterrano in coda.

E perché un encoder a cui non ti fidi è peggio di nessuno, ecco l'andata e ritorno che dimostra che le due direzioni concordano:

text <- "Hello, world!"
packed <- base64encode(charToRaw(text))
packed
#> [1] "SGVsbG8sIHdvcmxkIQ=="
identical(text, rawToChar(base64decode(packed)))
#> [1] TRUE

Stringhe, byte e la trappola del nome file

Adesso la trappola, perché ogni sviluppatore R ci casca una volta. base64encode() tratta un argomento character come un nome file, non come testo da codificare. Passagli una stringa e va a cercare quel file:

base64encode("Man")
#> Warning in file(what, "rb") :
#>   cannot open file 'Man': No such file or directory
#> Error: cannot open the connection
base64encode(charToRaw("Man"))
#> [1] "TWFu"

Il warning è il segnale: ha provato file("Man", "rb"), cioè "apri un file chiamato Man per lettura raw". Quindi con base64encode(), la disciplina è un solo riflesso: prima charToRaw(), sempre. Se un file è davvero ciò che intendi, è proprio ciò che la funzione sta facendo, e l'output sono i byte del file impacchettati, che a volte è esattamente ciò che vuoi.

b64 prende una posizione diversa sulla stessa questione: il suo encode() accetta un vettore di caratteri direttamente, tratta ogni elemento come testo UTF-8, ed è vettorizzata, per di più:

b64::encode("Man")
#> [1] "TWFu"
b64::encode(c("Man", "M"))
#> [1] "TWFu" "TQ=="

Altri due bordi di questo confine meritano di essere conosciuti. L'input vuoto viene codificato in tre modi diversi, a seconda di a chi chiedi:

base64encode(raw(0))
#> character(0)
b64::encode("")
#> [1] ""
base64url::base64_urlencode("")
#> [1] ""

base64enc risponde con un vettore di caratteri a lunghezza zero, non con una stringa vuota, quindi il codice a valle che si aspetta una stringa e riceve character(0) fallisce in posti sorprendenti. E la decisione sul charset vive anche sul lato codifica: charToRaw() impacchetta la stringa nella codifica che sta indossando in questo momento, quindi il testo UTF-8 viaggia come byte UTF-8, che è ciò che l'altro capo della rete si aspetta. Emoji incluse, perché il R moderno conserva i punti di codice sopra U+FFFF come vero UTF-8:

emoji <- "\U0001F600"
nchar(emoji, type = "bytes")
#> [1] 4
round_trip <- base64decode(base64encode(charToRaw(emoji)))
identical(emoji, rawToChar(round_trip))
#> [1] TRUE

A capo: MIME, PEM e la tua larghezza

Le stringhe Base64 lunghe vengono spezzate in righe, perché i canali di testo più antichi del mondo avevano limiti di colonne, e il MIME non ha mai trovato il tempo di dimenticarsene. Le due modalità di a capo storiche che incontrerai sono MIME, che va a capo a 76 caratteri con CRLF tra le righe, e PEM, che va a capo a 64. Ogni encoder ha la sua idea su quale usare, se ce n'è una, quindi questa è la sezione dove scegli il contratto prima di codificare.

Prima la matematica delle dimensioni, perché l'a capo riguarda il sapere quanto diventa lungo l'output. Escono quattro caratteri per ogni tre byte, il che rende la lunghezza della forma codificata un semplice arrotondamento all'insù:

nchar(base64encode(charToRaw(strrep("a", 100))))
#> [1] 136
4 * ceiling(100 / 3)
#> [1] 136

Con quello in tasca, gli encoder. base64enc è il più flessibile: di default emette una riga senza spezzature, e l'argomento linewidth ti consegna un vettore di righe al posto di quella:

long <- charToRaw(strrep("R", 100))
base64encode(long, linewidth = 76)
#> [1] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJS"
#> [2] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUg=="
base64encode(long, linewidth = 76, newline = "\r\n")
#> [1] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJS\r\nUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUg=="

Due righe per 100 byte a larghezza 76, un vettore di default, una singola stringa unita da CRLF quando aggiungi newline. Non c'è un elemento vuoto in coda: 114 byte, che si codificano in esattamente due righe da 76, tornano come due righe, non tre.

openssl prende l'altro polo. linebreaks = TRUE va a capo a 64 caratteri con a capo LF piani, e aggiunge un altro a capo alla fine:

wrapped <- openssl::base64_encode(charToRaw(strrep("a", 100)), linebreaks = TRUE)
nchar(wrapped)
#> [1] 139          # 136 caratteri di dati più 3 a capo
nchar(strsplit(wrapped, "\n", fixed = TRUE)[[1]])
#> [1] 64 64 8

Conta l'aritmetica: 136 caratteri di dati, due a capo interni, un a capo finale, 139 in totale. E quell'a capo finale è invisibile al conteggio delle righe più naturale che tu possa scrivere, perché strsplit() scarta un pezzo vuoto in coda, quindi il vettore dice tre righe mentre la stringa ne porta quattro. Se mai fai il diff tra un output a capo OpenSSL e uno a capo MIME e i conteggi dei caratteri non tornano, questo è il fantasma.

b64 non va a capo per niente; ti dà le due operazioni separatamente, e la larghezza del chunk ha una regola, un multiplo di quattro, perché il motore si rifiuta di tagliare un gruppo Base64 a metà:

enc <- b64::encode(strrep("a", 100))
ch <- b64::b64_chunk(enc, 76)
b64::b64_wrap(ch, "\r\n")
#> [1] "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\r\nYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ=="
b64::b64_chunk(enc, 75)
#> Error: Chunk size must be a multiple of 4.

E il pacchetto orientato ai file base64 segue l'esempio di OpenSSL: righe da 64 caratteri, un a capo finale, attivo di default:

writeBin(charToRaw(strrep("b", 200)), "big.bin")
base64::encode("big.bin", "big.b64")
con <- file("big.b64")
lines <- readLines(con)
close(con)
nchar(lines)
#> [1] 64 64 64 64 12  0

Quello zero finale è l'a capo finale, catturato da readLines() come un'ultima riga vuota. Ecco tutto il campo in una volta:

Encoder Larghezza A capo A capo finale
base64encode(x) una riga nessuno nessuno
base64encode(x, linewidth = 76, newline = "\r\n") 76 CRLF nessuno
openssl::base64_encode(x, linebreaks = TRUE) 64 LF sì
b64::encode(x) una riga nessuno (vai a capo con b64_chunk() e b64_wrap()) nessuno
base64::encode(in, out) 64 LF sì
base64url::base64_urlencode(x) una riga nessuno nessuno

Base64 sicuro per URL

Il Base64 standard spende le ultime due caselle del suo alfabeto su + e /, e sono esattamente i caratteri che gli URL non amano: il più diventa %2B, la barra diventa %2F, e ogni = di padding diventa %3D. La variante sicura per URL, definita nella sezione 5 della RFC 4648, scambia quelle due lettere con - e _ e di solito elimina il padding, quindi un token che dovrebbe essere incollabile ovunque resta incollabile ovunque. R ha tre porte verso di essa.

I motori di b64 sono i più completi: un motore è un alfabeto configurato e una politica di padding, e il pacchetto include i quattro che ti servono. Offri gli stessi tre byte al motore standard e a quello sicuro per URL e guarda l'alfabeto fare il suo lavoro:

bytes <- as.raw(c(0xfb, 0xef, 0xbe))
b64::encode(bytes)
#> [1] "++++"
b64::encode(bytes, engine("url_safe"))
#> [1] "----"
b64::encode(as.raw(0x4d), engine("url_safe"))
#> [1] "TQ=="
b64::encode(as.raw(0x4d), engine("url_safe_no_pad"))
#> [1] "TQ"

Quattro motori, "standard", "standard_no_pad", "url_safe" e "url_safe_no_pad", e lo stesso oggetto motore funziona in entrambe le direzioni, il che mantiene il tuo codice simmetrico. Le varianti senza padding divergono solo quando il padding apparirebbe davvero, come nell'esempio di un byte qui sopra.

Il pacchetto dedicato base64url è la porta a scopo unico: caratteri in ingresso, stringa sicura per URL in uscita, non va mai a capo, non fa mai padding:

base64url::base64_urlencode("hello world")
#> [1] "aGVsbG8gd29ybGQ"

E jose esporta il suo base64url_encode() per il lavoro con i JWT, restituendo vettori raw sul lato decodifica come ci si spera. Una regola lega tutte e tre le porte: l'alfabeto con cui codifichi è l'alfabeto con cui devi decodificare. Offri al decoder standard la stringa "----" e fallisce al primo trattino; l'articolo gemello sulla decodifica copre come ogni decoder incontra input sporchi o non corrispondenti.

JWT: creare il token

Il JSON Web Token è il punto in cui il Base64 sicuro per URL è diventato un mezzo da tutti i giorni. Un JWT è tre parti base64url unite da punti: un header che descrive come è stato firmato, un payload di claim JSON e una firma che lega le due. Sul lato codifica stai costruendo tutte e tre, e il pacchetto jose fa tutta la cerimonia. La sua API è costruita attorno alle funzioni jwt_* (jwt_claim(), jwt_encode_hmac(), jwt_decode_hmac(), jwt_split()); la release 2.0 (aprile 2026) aggiunge il supporto ED25519 e rende il campo header typ opzionale:

library(jose)
claim <- jwt_claim(iss = "app", sub = "1234567890", exp = Sys.time() + 3600)
token <- jwt_encode_hmac(claim, "0123456789abcdef")
sp <- jwt_split(token)
sp$header
#> $typ
#> [1] "JWT"
#>
#> $alg
#> [1] "HS256"
names(sp$payload)
#> [1] "iss" "sub" "exp" "iat"

Nota cosa ha fatto jwt_claim() dietro le quinte: iat (emesso a) ha come default l'ora corrente, quindi è apparso nel payload senza che glielo chiedessi, mentre exp ha come default il nulla, il che significa un token senza scadenza, che di solito non è ciò che vuoi. Imposta exp in modo deliberato, e la firma si occupa del resto. jwt_split() è lo strumento di ispezione: l'header, il payload come lista con nomi, e la firma raw, nessuna verifica coinvolta, esattamente il passo di sbirciatura che il lato decodifica di questa coppia di articoli descrive.

Il lato verifica è dove jose si guadagna da vivere. jwt_decode_hmac() controlla la firma e applica i claim temporali, e il suo stile di rifiuto è specifico:

round <- jwt_decode_hmac(token, "0123456789abcdef")
round$sub
#> [1] "1234567890"
jwt_decode_hmac(token, "ffffffffffffffff")
#> Error: HMAC signature verification failed!
old <- jwt_claim(sub = "1234567890", exp = Sys.time() - 3600)
expired <- jwt_encode_hmac(old, "0123456789abcdef")
jwt_decode_hmac(expired, "0123456789abcdef")
#> Error: Token has expired on 2026-08-30 05:09:36

Al successo ricevi i claim indietro come una lista ordinaria, quindi round$sub e soci funzionano così. Un claim futuro nbf (non prima di) si guadagna il suo rifiuto, Token is not valid before ..., e un token HMAC non verrà decodificato dall'asimmetrico jwt_decode_sig(), che risponde Unsupported algorithm: HMAC e vuole invece una chiave pubblica. Due note finali. Prima, il payload è codificato, non cifrato: chiunque può leggere ogni claim, quindi niente di segreto ha il posto lì dentro. Secondo, se carichi httr2 dopo jose, occhio al messaggio di mascheramento: httr2 esporta il suo jwt_claim(), jwt_encode_hmac() e jwt_encode_sig(), costruiti per le client credentials OAuth con exp che di default è cinque minuti in là, e mascherano le versioni jose per il resto della sessione.

File: binario in ingresso, testo in uscita

Codificare un file è lo specchio del lavoro sui file del lato decodifica, e il pacchetto b64 ha il punto d'ingresso più chiaro, che è anche il più veloce perché non costruisce mai una enorme stringa intermedia:

writeBin(charToRaw("file payload bytes"), "payload.bin")
enc <- b64::encode_file("payload.bin")
enc
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz"
cat(enc, file = "payload.b64")
readLines("payload.b64")
#> Warning in readLines("payload.b64") :
#>   incomplete final line found on 'payload.b64'
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz"

Il warning è una caratteristica in incognito: cat() non aggiunge un a capo finale, quindi il file finisce a metà riga, e readLines() te lo dice. Ricorda su che lato di questo fatto stai quando il file verrà consumato da b64::decode_file(), che va in panico su un a capo finale; l'articolo sulla decodifica ha la storia completa. Scrivi con cat() o writeBin(charToRaw(enc), path) e il bordo resta smussato.

Il pacchetto base64 è l'opzione pura da file a file, con la coppia di funzioni gemella e l'a capo in stile OpenSSL come default:

base64::encode("payload.bin", "payload2.b64")
readLines("payload2.b64")
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz" ""

Restituisce il percorso di output, che è comodo per il logging. E quando vuoi vedere la meccanica, la pipeline manuale funziona con qualsiasi encoder della tabella: leggi il file come raw, codifica, scrivi il testo, fatto:

bytes <- readBin("payload.bin", what = "raw", n = file.size("payload.bin"))
enc <- base64encode(bytes)
writeLines(enc, "payload3.b64")
identical(enc, readLines("payload3.b64"))
#> [1] TRUE

Data URI e documenti autosufficienti

Lo schema URI data: (RFC 2397) è il consumatore più visibile del lato codifica: un documento che porta con sé il proprio contenuto, un tipo MIME, la parola "base64" e il payload, tutto in un singolo attributo. I byte magici del PNG rendono il pattern riconoscibile: ogni PNG in Base64 in circolazione inizia con gli stessi caratteri, perché l'intestazione del file 89 50 4E 47 si codifica sempre nello stesso modo:

png_head <- as.raw(c(0x89, 0x50, 0x4e, 0x47))
uri <- paste0("data:image/png;base64,", base64encode(png_head))
uri
#> [1] "data:image/png;base64,iVBORw=="
tag <- paste0("<img src=\"", uri, "\" />")

Se hai mai cercato con grep in un file HTML iVBOR per trovare immagini incorporate, ecco perché l'impronta funziona. I report R Markdown, le dashboard a file singolo e le pagine estratte usano tutte la stessa forma, e costruirne uno segue lo stesso pattern: leggi il file come raw, codifica e incollalo dietro il prefisso MIME. Il costo è la matematica delle dimensioni di prima, un terzo più grande dell'originale, che resta nel tuo HTML per sempre, quindi tieni le immagini incorporate leggere.

API e richieste web

Il lato decodifica di questa coppia di articoli incontra API che ti consegnano Base64; questo lato incontra API che lo vogliono. Il pattern è sempre lo stesso: codifica i byte, metti la stringa nel corpo JSON e spediscila. Il client HTTP moderno è httr2:

library(httr2)
req <- request("https://httpbin.org/post")
req <- req_body_json(req, list(file = packed, name = "man.txt"))
req
#> <httr2_request>
#> POST https://httpbin.org/post
#> Body: JSON data
res <- req_perform(req)
res$status_code
#> [1] 200

Tre note per la strada. Il costruttore della richiesta è request(), e ha quel nome dalla prima release di httr2. Il corpo della risposta, quando leggi le risposte, arriva come vettore raw, quindi rawToChar() prima del parsing. E l'API può parlare un dialetto: alcune vogliono l'alfabeto sicuro per URL, alcune vogliono il padding rimosso, e un'API JSON non ha problemi con = in un valore di stringa, quindi la questione del padding diventa una questione di URL solo quando il Base64 viaggia nel path o nella query. Quando hai dubbi, leggi gli esempi dell'API piuttosto che la specifica nella tua testa.

Database, configurazione e ambiente

Il Base64 in un database è binario contrabbandato attraverso una colonna di testo, e il lato codifica è impacchettare un blob prima che entri. Ecco l'andata e ritorno contro SQLite attraverso DBI e RSQLite:

library(DBI)
library(RSQLite)
db <- dbConnect(SQLite(), ":memory:")
dbExecute(db, "CREATE TABLE files (name TEXT, payload TEXT)")
stored <- base64encode(charToRaw("stored in a database"))
dbExecute(db, paste0("INSERT INTO files VALUES ('note.txt', '", stored, "')"))
row <- dbGetQuery(db, "SELECT * FROM files")
rawToChar(base64decode(row$payload))
#> [1] "stored in a database"

L'alternativa è conservare i byte nativamente come BLOB, in cui caso il Base64 non serve affatto e la colonna torna in R come vettore raw. La variante Base64-in-TEXT esiste per portabilità: puoi ispezionarla con un editor di testo, farne il diff, e ogni altra lingua può leggerla senza driver binari. Quel stesso argomento la porta nella configurazione, dove un certificato o un segreto è conservato come stringa in YAML, JSON o in una variabile d'ambiente:

Sys.setenv("API_CERT" = base64encode(charToRaw("LTSSECRET")))
Sys.getenv("API_CERT")
#> [1] "TFRTU0VDUkVU"

Un'avvertenza sta qui, perché i file di configurazione sono il posto dove vivono i segreti: il Base64 è codifica, non cifratura. Un valore Base64 in un file di configurazione è leggibile da chiunque possa leggere il file. Sopravvive al trasporto e agli editor di testo; non protegge nulla.

Email

L'email è il posto dove è nato l'a capo da 76 caratteri, e gli allegati MIME lo portano ancora: contenuto Base64, spezzato a 76 caratteri con CRLF tra le righe, dentro una parte che dichiara Content-Transfer-Encoding: base64. L'encoder che produce esattamente quella forma è base64encode() con i suoi argomenti di a capo impostati sul contratto MIME:

body <- "The quick brown fox jumps over the lazy dog, and then it came back down again."
mime_part <- base64encode(charToRaw(body), linewidth = 76, newline = "\r\n")
strsplit(mime_part, "\r\n", fixed = TRUE)[[1]]
#> [1] "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgYW5kIHRoZW4gaXQg"
#> [2] "Y2FtZSBiYWNrIGRvd24gYWdhaW4u"

R non ha un client di posta di prima classe, ma il punto resta sempre che costruisci o ispezioni parti MIME a mano, generi fixture .eml, o estrai allegati da una di quelle: questa è la forma che il Base64 deve avere, e il lato decodifica di questa coppia di articoli mostra i decoder indulgenti che lo svolgono nel viaggio di ritorno.

Payload grossi e il soffitto delle stringhe

Le stringhe di R hanno un soffitto duro di 2^31 - 1 byte, e poiché la forma codificata è circa un terzo più grande dell'originale, un file di circa 1,5 GB di dati raw spingerebbe la sua codifica in una riga oltre il muro. La mossa pratica è la stessa che base64enc offre dalla sua release 2022 sui vettori lunghi: tieni l'output come righe, non come una stringa:

big <- raw(10 * 1024 * 1024)
packed <- base64encode(big, linewidth = 76)
length(packed)
#> [1] 183961
sum(nchar(packed))
#> [1] 13981016

Dieci megabyte di zero diventano 183961 righe da al massimo 76 caratteri, un vettore perfettamente ordinario che puoi scrivere riga per riga o far scorrere in pipe senza mai tenere una stringa enorme. Per il caso dei data frame, una colonna di molti valori binari, b64 è il campione di velocità: il suo motore Rust codifica l'intera colonna in una chiamata vettorizzata, che è una differenza drastica rispetto al loop riga per riga. Se hai una colonna di valori codificati da produrre, fai tu un confronto rapido con system.time(); il divario tra un loop riga per riga e una chiamata vettorizzata è di solito abbastanza grande da contare.

La riga di comando

Non serve una sessione R completa per tutto. Il classico tool Unix parla Base64 nativamente, codificando senza flag su ogni piattaforma, e decodificando con -d su Linux e -D su macOS e i BSD:

echo -n "Hello, world!" | base64
#> SGVsbG8sIHdvcmxkIQ==
echo -n "SGVsbG8sIHdvcmxkIQ==" | base64 -d
#> Hello, world!

Occhio al -n sulla prima riga: senza, echo contribuisce con un a capo, e l'output codifica 14 byte invece di 13, finendo in o= invece di IQ==. Un base64 GNU va anche a capo per te (-w 76), che è comodo quando fai pipe in qualcosa che si aspetta input con la forma MIME. E un Rscript di una riga fa lo stesso lavoro con gli stessi pacchetti che usi nei tuoi script:

Rscript -e 'library(base64enc); writeLines(base64encode(charToRaw("Man")))'
#> TWFu

Usa la shell per controlli rapidi e pipe; usa R quando il risultato deve vivere in un data frame, un file o un report. E non incollare stringhe da molti megabyte nel terminale: gli argomenti della riga di comando colpiscono ARG_MAX molto prima del Base64, quindi, invece, fai pipe attraverso un file.

Le trappole che conviene conoscere

Ecco l'elenco breve dei modi in cui il lato codifica morde gli sviluppatori R, tutti nativi dell'ecosistema e non del Base64 in generale:

  • Una stringa è un nome file. base64encode("Man") prova ad aprire un file chiamato Man. Il warning indica il file; l'errore dice che la connessione è fallita. Usa prima charToRaw() con base64encode().
  • L'input vuoto si codifica in tre modi. base64encode(raw(0)) restituisce character(0), mentre b64::encode("") e base64url::base64_urlencode("") restituiscono "". Il codice a valle su stringhe non si aspetta un vettore a lunghezza zero.
  • openssl va a capo e poi aggiunge una riga fantasma. linebreaks = TRUE spezza a 64 con LF e aggiunge un a capo finale che strsplit() scarta in silenzio, quindi i conteggi di righe e di caratteri fatti con ingenuità divergono di una riga.
  • L'a capo è un contratto. Il MIME è 76 con CRLF, il PEM è 64, le API JSON di solito non vogliono niente affatto. Scegli la forma che l'altro lato si aspetta, perché un decoder che tollera uno stile di a capo ne rifiuterà un altro.
  • L'alfabeto sicuro per URL deve corrispondere su entrambi i capi. "----" codificato sicuro per URL fallisce in un decoder standard al primo trattino, e il padding diventa %3D nel momento in cui la stringa vive in un URL.
  • b64_chunk chiede i multipli di quattro. Qualsiasi altra larghezza guadagna Chunk size must be a multiple of 4., perché un gruppo Base64 non si può tagliare a metà.
  • Un a capo finale può far andare in panico il decoder. I file che scrivi perché b64::decode_file() li legga devono finire senza a capo; cat(), non writeLines().
  • I claim temporali dei JWT sono applicati. jwt_decode_hmac() rifiuta token scaduti e claim nbf futuri, e httr2 maschera le funzioni jwt_* di jose se lo carichi per secondo.
  • Il payload è visibile. I claim Base64 in un JWT, un data URI o un file di configurazione sono leggibili da chiunque. La codifica non è cifratura.
  • Il soffitto delle stringhe è un muro, non un'indicazione. Circa 1,5 GB di dati raw per stringa R è il punto in cui la codifica in una riga smette di stare; tieni gli output grossi come righe o falli scorrere.

Buone pratiche

  • Converti con charToRaw() prima di codificare quando usi base64enc::base64encode(). Se ti serve input di caratteri diretto e vettorizzazione, b64::encode() è il pacchetto che tratta le stringhe come stringhe.
  • Per il lavoro di tutti i giorni usa base64enc::base64encode() come default, rivolgiti a b64 quando vuoi velocità, vera vettorizzazione o i motori sicuri per URL, e usa openssl quando è già nel progetto e vuoi un output con la forma PEM.
  • Scegli l'a capo per canale, non per pacchetto: niente per JSON e URL, 76 con CRLF per MIME, 64 per blocchi in stile PEM, e resta coerente così il lato decoder della pipe sa cosa aspettarsi.
  • Usa l'alfabeto sicuro per URL senza padding per tutto ciò che vivrà in un URL, un JWT o un nome file, e l'alfabeto standard per l'email.
  • Per i JWT, imposta exp (e iat) esplicitamente, verifica con jwt_decode_hmac() prima di fidarti di un token, e ricorda che ogni claim è testo pubblico.
  • Metti alla prova i tuoi percorsi di codifica con un'andata e ritorno, identical(text, rawToChar(base64decode(base64encode(charToRaw(text))))); è una riga e cattura in un colpo errori di alfabeto, padding e charset.
  • Per i file, preferisci b64::encode_file() o base64::encode() al leggere l'intero file in una stringa, e scrivi il tuo output con cat() quando a leggerlo sarà un decoder severo.
  • Tieni il nastro imballaggio onesto: il Base64 fa viaggiare i byte, non li rende segreti e non li rende più piccoli. Cifra prima se la segretezza è l'obiettivo, comprimi prima se lo è le dimensioni.

Come il Base64 è entrato in R

Il formato è arrivato molto prima che R facesse qualcosa con esso. È stato standardizzato per il protocollo Privacy-Enhanced Mail nel 1987 (RFC 989), adottato dal MIME nel 1993 (RFC 1521, poi la definitiva RFC 2045 del 1996, che definisce ancora l'a capo da 76 caratteri), rimesso in ordine nella RFC 3548 nel 2003, e ha assunto la sua forma moderna nella RFC 4648 del 2006, che ha aggiunto l'alfabeto sicuro per URL, l'opzione senza padding e il suo fratello minore Base32. La storia di R è iniziata nel settembre 2012, quando il base64enc di Simon Urbanek è atterrato su CRAN ed è diventato silenziosamente la risposta di default a "come faccio a fare il Base64 di questa" per oltre un decennio, aggiungendo checkUTF8() nel 2015 e il supporto per i vettori lunghi nel 2022. Il mondo della crittografia è entrato attraverso openssl, il wrapper di lunga data di Jeroen Ooms attorno all'OpenSSL di sistema, il cui base64_encode() è l'opzione con la forma PEM da allora. Il vecchio pacchetto base64, anche questo di Ooms, è stato ripubblicato nell'ottobre 2024 esplicitamente come wrapper di compatibilità, la sua descrizione ora indirizza le nuove applicazioni altrove. Poi è arrivato b64 a gennaio 2024, un motore Rust costruito con extendr che ha portato la vettorizzazione e una scuderia di alfabeti, e nell'aprile 2026 jose ha rilasciato la versione 2.0, aggiungendo il supporto ED25519 all'API jwt_* e rendendo i token firmati un cittadino di prima classe. Il risultato è una cassetta degli attrezzi con un encoder per ogni lavoro: stringhe di tutti i giorni, blocchi PEM, velocità, URL, file e token.

Angolo curioso

Perché una guida completa dovrebbe finire con un sorriso:

  • Ogni PNG in Base64 su internet inizia con gli stessi caratteri: l'intestazione magica 89 50 4E 47 si codifica in iVBOR, quindi cercare con grep in un file HTML quell'impronta trova ogni immagine incorporata. I formati hanno impronte digitali, e questa è un prefisso.
  • base64encode("Man") non codifica la parola Man. Va a cercare un file chiamato Man, avverte che non riesce ad aprirlo e molla. La trappola più specifica di R in assoluto nell'ecosistema Base64, nascosta in bella vista nell'elenco degli argomenti.
  • Una stringa a capo OpenSSL finisce sempre con un a capo finale, quindi l'ultima riga di un blocco PEM non è mai l'ultima riga del file. L'a capo ha un punto a fine frase, che tu lo voglia o no.
  • La stringa vuota si codifica in tre modi diversi: base64enc restituisce un vettore di zero stringhe, b64 e base64url restituiscono la stringa vuota. R incontra il nulla, e R dà tre risposte.
  • jose scrive l'header del JWT con typ prima di alg, mentre la maggior parte degli esempi scritti a mano mette alg per primo. Il JSON non si cura dell'ordine delle chiavi, e la verifica dei JWT lo sa, ma il tuo diff di stringhe no.
  • Il limite di 76 caratteri del MIME è una decisione del 1993 sulla lunghezza delle righe dei messaggi, portata attraverso quattro RFC in ogni allegato email che hai mai inviato. L'a capo che stai impostando oggi era dibattuto prima che R avesse un grafico a colori.
  • b64 decodificherà alfabeti che non hai mai visto, BinHex, IMAP modified UTF-7, bcrypt, crypt e la coppia sicura per URL, con un motore per ciascuno. Lo stesso codice Rust che impacchetta il tuo campo JSON può disimballare un allegato Macintosh degli anni '80.

Per concludere

Scegli il tuo encoder in base al lavoro: base64enc per le stringhe di tutti i giorni, con linewidth e newline quando il canale ha una forma; openssl quando vuoi un output in stile PEM da 64 caratteri o è già nel progetto; b64 quando vuoi velocità, vettori o i motori sicuri per URL; e i piccoli specialisti base64 e base64url per le faccende dei file e le stringhe sicure per URL. Converti con charToRaw() prima di codificare con base64enc::base64encode(), scegli l'a capo per canale piuttosto che per pacchetto, tieni l'alfabeto sicuro per URL per tutto ciò che vivrà in un URL o in un JWT, firma e verifica i token con jose, e metti alla prova ogni percorso con un'andata e ritorno. Il formato in sé è implacabile in esattamente due posti, l'alfabeto e gli a capo, e gli encoder differiscono soprattutto per quanto onestamente ti dicono quando ne hai sbagliato uno. E quando chiama la direzione opposta - quando arriva una stringa e devi smontarla, controllare cosa significano i byte, e sopravvivere ai decoder che falliscono in silenzio - l'articolo gemello copre la decodifica Base64 in R nel dettaglio.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in R: una guida completa