¿Tiene que ocuparse del formato Base64? Entonces esta página es perfecta para Ud. Utilice nuestra práctica herramienta en línea para codificar o decodificar sus datos.

Codificación Base64 en R: una guía completa

Tienes bytes que necesitan viajar, y el camino solo permite texto. Un JPEG que tiene que vivir dentro de un campo JSON. Un certificado que tiene que estar sentado en una variable de entorno. Un plot que tiene que viajar dentro de un informe HTML autocontenido. Base64 es la cinta de empaquetar para todo ello: cualquier secuencia de bytes se convierte en una cadena de 64 caracteres inofensivos que sobrevive a cualquier canal de texto que le sueltes. La página de inicio de este sitio cubre el formato a fondo, así que aquí va la versión corta: tres bytes entran, cuatro caracteres salen, tomados de la A a la Z, de la a a la z, del 0 al 9, más + y /, con un poquito de padding = al final cuando la carga no se reparte exactamente.

El giro propio de R: R base no incluye ningún codificador Base64. No hay ningún base64_encode() esperando en un paquete base, y no hay ningún builtin de una línea al que puedas recurrir. Eliges un paquete, y el ecosistema te da una elección de verdad, con velocidades distintas, hábitos de envolvente distintos, y opiniones distintas sobre el padding. Al final de este artículo sabrás a qué codificador acudir en cada situación, y cuál de ellos hará en silencio algo que no es codificar.

El panorama de los codificadores

Cinco paquetes hacen la codificación, y se dividen en los caballos de batalla del día a día, los vecinos de la criptografía, y los especialistas pequeños. Aquí está el reparto, vigente a fecha de 2026:

Paquete Versión (2026) Puntos de entrada para codificar Hábitos de envolvente Cuándo acudir a él
base64enc 0.1-6 base64encode() linewidth y newline, enteramente a tu gusto cadenas del día a día, envolvente MIME
openssl 2.4.2 base64_encode() líneas de 64 caracteres, saltos LF, salto de línea final archivos PEM, pilas cripto ya en uso
b64 0.1.7 encode(), encode_file() nunca envuelve; b64_chunk() y b64_wrap() bajo demanda velocidad, vectores, motores URL-safe
base64 2.0.2 encode() líneas de 64 caracteres más un salto de línea final por defecto menudos de archivo a archivo, imágenes de informes
base64url 1.4 base64_urlencode() nunca envuelve, sin padding, entra un carácter cadenas URL-safe

Tres codificadores más se esconden dentro de paquetes que quizá ya cargues. jsonlite exporta base64_enc() y base64url_enc(), así que si ya analizas JSON, quizá ya tengas un codificador a mano. jose exporta base64url_encode() para el trabajo con JWT. Y el veterano RCurl sigue llevando un envoltorio base64() sobre libcurl que funciona bien y pertenece a una era anterior. El paquete base64, por fin, ahora se describe a sí mismo en su etiqueta como envoltorio de compatibilidad y señala a las aplicaciones nuevas hacia base64enc, openssl o jsonlite.

Puesta en marcha

Si R aún no está en la máquina, tu sistema operativo lo incluye: r-base en Debian y Ubuntu, R en Fedora, Homebrew o el instalador oficial en macOS, un instalador en Windows. Y luego los codificadores, directos de CRAN:

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

Los dos sitios donde las instalaciones se tuercen son en tiempo de compilación. openssl se compila contra el OpenSSL de tu sistema, así que una máquina Linux desnuda quiere primero las cabeceras de desarrollo (sudo apt install libssl-dev), o puedes saltarte la compilación por completo en Debian y Ubuntu con sudo apt install r-cran-openssl. Y b64 es un motor Rust envuelto con extendr, así que una compilación desde fuente quiere el toolchain de Rust (sudo apt install cargo trae también rustc). Windows y macOS reciben binarios precompilados de CRAN y nada de esto aplica.

La primera codificación

El noventa por ciento de la vida de codificación cabe en tres líneas, usando la misma célebre cadena que el lado de decodificación usa como su prueba de humo:

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

Tres cosas a notar en esa ceremonia. Primera, la entrada es un vector raw: charToRaw() es el puente de tu cadena R a los bytes que se empaquetan, y los codificadores del día a día de esta tabla - base64enc, openssl, b64 - todos toman raw. Segunda, la salida va en la dirección opuesta a los decodificadores: una única cadena de caracteres, porque la codificación termina en el lado del texto de la frontera. Tercera, mira la cuenta de longitudes en acción: tres bytes entran, cuatro caracteres salen, sin padding porque la carga se reparte exactamente. Cuando la carga no se reparte, uno o dos caracteres = caen al final.

Y porque un codificador en el que no confías es peor que ninguno, aquí está el viaje de ida y vuelta que demuestra que las dos direcciones coinciden:

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

Cadenas, bytes y la trampa del nombre de archivo

Ahora la trampa, porque cada desarrollador de R cae en ella una vez. base64encode() trata un argumento character como un nombre de archivo, no como texto para codificar. Pásale una cadena y se pone a buscar ese archivo:

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"

El aviso es la pista: intentó file("Man", "rb"), que significa "abrir un archivo llamado Man para lectura raw". Así que con base64encode(), la disciplina es un único reflejo: charToRaw() primero, siempre. Si un archivo es realmente lo que quieres decir, esa es la función que está cumpliendo, y la salida son los bytes del archivo empaquetados, que a veces es exactamente lo que buscas.

b64 toma una posición diferente sobre la misma pregunta: su encode() acepta un vector de caracteres directamente, trata cada elemento como texto UTF-8, y encima está vectorizado:

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

Dos filos más de esta frontera merecen la pena conocerlos. La entrada vacía se codifica de tres maneras distintas, según a quién preguntes:

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

base64enc responde con un vector de caracteres de longitud cero, no con una cadena vacía, así que el código aguas abajo que espera una cadena y recibe character(0) falla en sitios sorprendentes. Y la decisión del charset vive también en el lado de codificación: charToRaw() empaqueta la cadena en la codificación que lleva puesta, así que el texto UTF-8 viaja como bytes UTF-8, que es lo que espera el otro extremo del cable. Emoji incluidos, ya que el R moderno almacena los puntos de código por encima de U+FFFF como UTF-8 de verdad:

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

El ajuste de líneas: MIME, PEM y tu propio ancho

Las cadenas Base64 largas se rompen en líneas, porque los canales de texto más antiguos del mundo tenían límites de columnas, y MIME nunca tuvo tiempo de olvidarlos. Los dos envolventes históricos que te encontrarás son MIME, que corta a los 76 caracteres con CRLF entre líneas, y PEM, que corta a 64. Cada codificador tiene su propia idea de cuál usar, si es que usa alguno, así que esta es la sección donde eliges el contrato antes de codificar.

Primero la cuenta del tamaño, porque el ajuste de líneas va de saber cuánto se alarga la salida. Por cada tres bytes salen cuatro caracteres, lo que convierte la longitud de la forma codificada en un techo simple:

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

Con eso en el bolsillo, los codificadores. base64enc es el más flexible: por defecto emite una línea sin cortar, y el argumento linewidth te entrega un vector de líneas en su lugar:

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

Dos líneas para 100 bytes con ancho 76, un vector por defecto, y una única cadena unida con CRLF cuando añades newline. No hay un elemento vacío final: 114 bytes, que codifican exactamente en dos líneas de 76, vuelven como dos líneas, no como tres.

openssl toma el otro polo. linebreaks = TRUE envuelve a 64 caracteres con saltos LF llanos, y añade un salto de línea más al final de todo:

wrapped <- openssl::base64_encode(charToRaw(strrep("a", 100)), linebreaks = TRUE)
nchar(wrapped)
#> [1] 139          # 136 caracteres de datos más 3 saltos de línea
nchar(strsplit(wrapped, "\n", fixed = TRUE)[[1]])
#> [1] 64 64 8

Cuenta la aritmética: 136 caracteres de datos, dos saltos internos, un salto final, 139 en total. Y ese salto final es invisible para el recuento de líneas más natural que puedes escribir, porque strsplit() descarta un trozo vacío final, así que el vector dice tres líneas mientras la cadena lleva cuatro. Si algún día comparas una salida envolvente de OpenSSL con una envolvente de MIME y los recuentos de caracteres no cuadran, este es el fantasma.

b64 no envuelve para nada; te da las dos operaciones por separado, y el ancho de fragmento tiene una regla, múltiplo de cuatro, porque el motor se niega a cortar un grupo Base64 por la mitad:

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.

Y el paquete base64 orientado a archivos sigue la estela de OpenSSL: líneas de 64 caracteres, un salto de línea final, activado por defecto:

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

Ese cero final es el salto de línea al final, atrapado por readLines() como una última línea vacía. Aquí está todo el campo de una vez:

Codificador Ancho Salto de línea Salto de línea final
base64encode(x) una línea ninguno ninguno
base64encode(x, linewidth = 76, newline = "\r\n") 76 CRLF ninguno
openssl::base64_encode(x, linebreaks = TRUE) 64 LF
b64::encode(x) una línea ninguno (envuelve con b64_chunk() y b64_wrap()) ninguno
base64::encode(in, out) 64 LF
base64url::base64_urlencode(x) una línea ninguno ninguno

Base64 URL-safe

El Base64 estándar gasta las dos últimas plazas de su alfabeto en + y /, y esos son exactamente los caracteres que a las URLs no les encantan: el más se vuelve %2B, la barra se vuelve %2F, y cada = de padding se vuelve %3D. La variante URL-safe, definida en la sección 5 del RFC 4648, cambia esas dos letras por - y _ y normalmente suelta el padding, así que un token que debería pegarse en cualquier sitio sigue pegándose en cualquier sitio. R tiene tres puertas hacia él.

Los motores b64 son los más completos: un motor es un alfabeto y una política de padding configurados, y el paquete incluye los cuatro que necesitas. Dale los mismos tres bytes al motor estándar y al URL-safe y mira cómo hace su trabajo el alfabeto:

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"

Cuatro motores, "standard", "standard_no_pad", "url_safe" y "url_safe_no_pad", y el mismo objeto motor funciona en ambas direcciones, lo que mantiene tu código simétrico. Las variantes sin padding solo difieren cuando el padding aparecería de verdad, como en el ejemplo de un byte de arriba.

El paquete base64url dedicado es la puerta de un solo propósito: entra un carácter, sale una cadena URL-safe, nunca envuelve, nunca añade padding:

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

Y jose exporta su propio base64url_encode() para el trabajo con JWT, devolviendo vectores raw en el lado de decodificación como se podría esperar. Una regla ata las tres puertas: el alfabeto con el que codificas es el alfabeto con el que debes decodificar. Dale al decodificador estándar la cadena "----" y falla en el primer guion; el artículo hermano de decodificación cubre cómo se topa cada decodificador con la entrada sucia o desajustada.

JWTs: fabricando el token

El JSON Web Token es donde el Base64 URL-safe se volvió un uso diario. Un JWT son tres partes base64url unidas por puntos: un header que describe cómo se firmó, un payload de claims JSON, y una firma que ata las dos. En el lado de codificación estás construyendo las tres, y el paquete jose hace toda la ceremonia. Su API está construida en torno a las funciones jwt_* (jwt_claim(), jwt_encode_hmac(), jwt_decode_hmac(), jwt_split()); la release 2.0 (abril de 2026) añade soporte ED25519 y hace opcional el campo typ del header:

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"

Fíjate en lo que jwt_claim() hizo por detrás: iat (emitido en) por defecto es la hora actual, así que apareció en el payload sin que se lo pidieras, mientras exp por defecto no es nada, lo que significa un token sin caducidad, que normalmente no es lo que quieres. Fija exp con deliberación, y la firma se encarga del resto. jwt_split() es la herramienta de inspección: el header, el payload como lista nombrada, y la firma raw, sin verificación implicada, exactamente el paso de asomarse que describe el lado de decodificación de este par de artículos.

El lado de verificación es donde jose gana su sustento. jwt_decode_hmac() comprueba la firma y aplica los claims de tiempo, y su estilo de negativa es específico:

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

Con éxito recuperas los claims como una lista corriente, así que round$sub y compañía funcionan tal cual. Un claim nbf (no antes de) en el futuro se gana su propia negativa, Token is not valid before ..., y un token HMAC no será decodificado por el jwt_decode_sig() asimétrico, que responde Unsupported algorithm: HMAC y en su lugar quiere una clave pública. Dos notas finales. Primera, el payload está codificado, no cifrado: cualquiera puede leer cada claim, así que nada secreto tiene que ver ahí. Segunda, si cargas httr2 después de jose, fíjate en el mensaje de enmascaramiento: httr2 exporta su propio jwt_claim(), jwt_encode_hmac() y jwt_encode_sig(), construidos para credenciales de cliente OAuth con exp por defecto a cinco minutos vista, y eclipsan a las versiones de jose para el resto de la sesión.

Archivos: binario dentro, texto fuera

Codificar un archivo es el espejo del trabajo de archivos del lado de decodificación, y el paquete b64 tiene el punto de entrada más claro, que además es el más rápido porque nunca construye una cadena intermedia enorme:

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"

El aviso es una característica disfrazada: cat() no añade un salto de línea final, así que el archivo termina a medio camino de una línea, y readLines() te lo dice. Recuerda de qué lado de este hecho estás cuando el archivo lo va a consumir b64::decode_file(), que entra en pánico con un salto de línea final; el artículo de decodificación tiene la historia completa. Escribe con cat() o writeBin(charToRaw(enc), path) y el filo se queda sordo.

El paquete base64 es la opción pura de archivo a archivo, con el par de funciones a juego y el envolvente al estilo OpenSSL por defecto:

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

Devuelve la ruta de salida, lo cual es práctico para el registro. Y cuando quieras ver la maquinaria, la cadena de trabajo manual funciona con cualquier codificador de la tabla: lee el archivo como raw, codifica, escribe el texto, listo:

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 URIs y documentos autocontenidos

El esquema de URI data: (RFC 2397) es el consumidor más visible del lado de codificación: un documento que lleva su propio contenido, un tipo MIME, la palabra "base64" y el payload, todo en un solo atributo. Los bytes mágicos de PNG hacen el patrón reconocible: cada PNG Base64 del mundo empieza con los mismos caracteres, porque la cabecera de archivo 89 50 4E 47 se codifica siempre igual:

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, "\" />")

Si alguna vez has hecho grep a un archivo HTML buscando iVBOR para encontrar imágenes incrustadas, esa es la razón de que la huella funcione. Informes R Markdown, dashboards de un solo archivo y páginas raspadas usan todos la misma forma, y construir uno sigue el mismo patrón: lee el archivo como raw, codifica, y pégalo detrás del prefijo MIME. El coste es la cuenta del tamaño de antes, un tercio más grande que el original, viviendo en tu HTML para siempre, así que mantén las imágenes incrustadas ligeras.

APIs y peticiones web

El lado de decodificación de este par de artículos se topa con APIs que te entregan Base64; este lado se topa con APIs que lo piden. El patrón es el mismo cada vez: codifica los bytes, mete la cadena en el cuerpo JSON, envíalo. El cliente HTTP moderno es 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

Tres notas para el camino. El constructor de peticiones es request(), y lleva ese nombre desde la primera release de httr2. El cuerpo de la respuesta, cuando lees respuestas, llega como vector raw, así que rawToChar() antes de analizar. Y la API puede hablar un dialecto: algunas quieren el alfabeto URL-safe, algunas quieren el padding arrancado, y una API JSON no tiene ningún problema con = en el valor de una cadena, así que la pregunta del padding solo se vuelve pregunta de URL cuando el Base64 viaja en la ruta o en la query. Cuando dudes, lee los ejemplos de la API en vez de la especificación de tu cabeza.

Bases de datos, configuración y variables de entorno

Base64 en una base de datos es binario contrabandeado a través de una columna de texto, y el lado de codificación es empaquetar un blob antes de meterlo. Aquí está el viaje de ida y vuelta contra SQLite a través de DBI y 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"

La alternativa es almacenar los bytes de forma nativa como BLOB, en cuyo caso no hace falta Base64 para nada y la columna vuelve a R como vector raw. La variante Base64-en-TEXT existe por portabilidad: puedes inspeccionarla con un editor de texto, compararla con diff, y cualquier otro lenguaje puede leerla sin drivers binarios. Ese mismo argumento la lleva a la configuración, donde un certificado o un secreto se almacena como cadena en YAML, JSON o una variable de entorno:

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

Una advertencia pertenece aquí, porque los archivos de configuración son donde viven los secretos: Base64 es codificación, no cifrado. Un valor Base64 en un archivo de configuración lo puede leer cualquiera que pueda leer el archivo. Sobrevive al transporte y a los editores de texto; no protege nada.

Email

El email es donde nació el envolvente de 76 caracteres, y los adjuntos MIME todavía lo llevan puesto: contenido Base64, cortado a 76 caracteres con CRLF entre líneas, dentro de una parte que declara Content-Transfer-Encoding: base64. El codificador que produce exactamente esa forma es base64encode() con sus argumentos de envolvente fijados al contrato 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 no tiene un cliente de correo de primera clase, pero la idea sigue en pie cada vez que construyes o inspeccionas partes MIME a mano, generas fixtures .eml, o extraes adjuntos de uno: esta es la forma que el Base64 tiene que tener, y el lado de decodificación de este par de artículos muestra a los decodificadores permisivos desenvolviéndolo de vuelta.

Payloads grandes y el techo de las cadenas

Las cadenas R tienen un techo duro de 2^31 - 1 bytes, y como la forma codificada es un tercio más grande que el original, un archivo de unos 1,5 GB de datos raw empujaría su codificación de una línea más allá del muro. La jugada práctica es la misma que base64enc ofrece desde su release de vectores largos de 2022: mantén la salida como líneas, no como una cadena:

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

Diez megabytes de ceros se convierten en 183961 líneas de a lo sumo 76 caracteres, un vector perfectamente corriente que puedes escribir línea a línea o hacer pasar por una tubería sin sostener nunca una cadena enorme. Para el caso de data frame, una columna de muchos valores binarios, b64 es el campeón de velocidad: su motor Rust codifica la columna entera en una llamada vectorizada, que es una diferencia dramática frente a iterar fila a fila. Si tienes una columna de valores codificados que producir, corre tú mismo una comparación rápida con system.time(); la brecha entre un bucle por fila y una llamada vectorizada suele ser bastante grande como para importar.

La línea de comandos

No todo necesita una sesión R completa. La herramienta Unix clásica habla Base64 nativamente, codificando sin banderas en todas las plataformas, y decodificando con -d en Linux y con -D en macOS y los BSD:

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

Fíjate en el -n de la primera línea: sin él, echo aporta un salto de línea, y la salida codifica 14 bytes en vez de 13, terminando en o= en vez de IQ==. Un base64 de GNU también envuelve por ti (-w 76), lo cual es práctico al pasar por tubería a algo que espera entrada con forma MIME. Y un Rscript de una línea hace el mismo trabajo con los mismos paquetes que usas en tus scripts:

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

Usa la shell para comprobaciones rápidas y tuberías; usa R cuando el resultado necesite vivir en un data frame, un archivo o un informe. Y no pegues cadenas de varios megabytes en el terminal: los argumentos de línea de comandos tocan ARG_MAX mucho antes que el Base64, así que pásalas por un archivo en su lugar.

Trampas que conviene conocer

Aquí está la lista corta de las formas en que el lado de codificación muerde a los desarrolladores de R, todas nativas del ecosistema y no de Base64 en general:

  • Una cadena es un nombre de archivo. base64encode("Man") intenta abrir un archivo llamado Man. El aviso nombra el archivo; el error dice que la conexión falló. Usa charToRaw() primero con base64encode().
  • La entrada vacía se codifica de tres maneras. base64encode(raw(0)) devuelve character(0), mientras b64::encode("") y base64url::base64_urlencode("") devuelven "". El código de cadenas aguas abajo no espera un vector de longitud cero.
  • openssl envuelve y luego añade una línea fantasma. linebreaks = TRUE corta a 64 con LF y añade un salto de línea final que strsplit() descarta en silencio, así que los recuentos de líneas ingenuos y los de caracteres discrepan en una línea.
  • El envolvente es un contrato. MIME es 76 con CRLF, PEM es 64, las APIs JSON normalmente no quieren nada. Elige la forma que el otro lado espera, porque un decodificador que tolera un estilo de envolvente rechaza otro.
  • El alfabeto URL-safe debe coincidir en los dos extremos. "----" codificado URL-safe falla en un decodificador estándar en el primer guion, y el padding se vuelve %3D en el momento en que la cadena vive en una URL.
  • b64_chunk exige múltiplos de cuatro. Cualquier otro ancho se gana Chunk size must be a multiple of 4., porque un grupo Base64 no puede cortarse por la mitad.
  • Un salto de línea final puede poner en pánico al decodificador. Los archivos que escribas para que los lea b64::decode_file() deben terminar sin salto de línea; cat(), no writeLines().
  • Los claims de tiempo de JWT se aplican. jwt_decode_hmac() rechaza tokens caducados y claims nbf futuros, y httr2 enmascara las funciones jwt_* de jose si lo cargas segundo.
  • El payload es visible. Los claims Base64 de un JWT, un data URI o un archivo de configuración los puede leer cualquiera. Codificar no es cifrar.
  • El techo de las cadenas es un muro, no una orientación. Unos 1,5 GB de datos raw por cadena R es donde deja de caber una codificación de una línea; mantén las salidas grandes como líneas o hazlas pasar por streaming.

Prácticas recomendadas

  • Convierte con charToRaw() antes de codificar cuando uses base64enc::base64encode(). Si necesitas entrada directa de caracteres y vectorización, b64::encode() es el paquete que trata las cadenas como cadenas.
  • Por defecto, usa base64enc::base64encode() para el trabajo de cada día, acude a b64 cuando quieras velocidad, vectorización de verdad o los motores URL-safe, y usa openssl cuando ya esté en el proyecto y quieras una salida con forma PEM.
  • Elige el envolvente por canal, no por paquete: nada para JSON y URLs, 76 con CRLF para MIME, 64 para bloques al estilo PEM, y mantén la coherencia para que el lado decodificador de la tubería sepa qué esperar.
  • Usa el alfabeto URL-safe sin padding para lo que viva en una URL, un JWT o un nombre de archivo, y el alfabeto estándar para el email.
  • Para JWTs, fija exp (y iat) explícitamente, verifica con jwt_decode_hmac() antes de confiar en un token, y recuerda que cada claim es texto público.
  • Prueba tus rutas de codificación con un viaje de ida y vuelta, identical(text, rawToChar(base64decode(base64encode(charToRaw(text))))); es una línea y atrapa errores de alfabeto, padding y charset a la vez.
  • Para archivos, prefiere b64::encode_file() o base64::encode() antes que leer el archivo entero en una cadena, y escribe tu salida con cat() cuando un decodificador estricto la va a leer.
  • Mantén la cinta de empaquetar honesta: Base64 hace viajar los bytes, no los hace secretos y no los hace más pequeños. Cifra primero si el objetivo es el secreto, comprime primero si es el tamaño.

Cómo entró Base64 en R

El formato llegó mucho antes de que R hiciera nada con él. Fue estandarizado para el protocolo Privacy-Enhanced Mail en 1987 (RFC 989), adoptado por MIME en 1993 (RFC 1521, y luego el definitivo RFC 2045 en 1996, que todavía define el envolvente de 76 caracteres), ordenado en RFC 3548 en 2003, y dado su forma moderna en RFC 4648 en 2006, que añadió el alfabeto URL-safe, la opción sin padding, y su hermano menor Base32. La historia de R empezó en septiembre de 2012, cuando el base64enc de Simon Urbanek aterrizó en CRAN y se volvió en silencio la respuesta por defecto a "¿cómo hago Base64 a esto?" durante más de una década, ganando checkUTF8() en 2015 y el soporte de vectores largos en 2022. El mundo cripto entró por openssl, el envoltorio a largo plazo de Jeroen Ooms sobre el OpenSSL del sistema, cuyo base64_encode() ha sido la opción con forma PEM desde entonces. El viejo paquete base64, también de Ooms, se reeditó en octubre de 2024 explícitamente como envoltorio de compatibilidad, con su propia descripción señalando ahora a las aplicaciones nuevas a otra parte. Luego b64 llegó en enero de 2024, un motor Rust construido con extendr que trajo la vectorización y una cuadra de alfabetos, y en abril de 2026 jose publicó la versión 2.0, añadiendo soporte ED25519 a la API jwt_* y convirtiendo a los tokens firmados en ciudadanos de primera clase. El resultado es un arcón de herramientas con un codificador por trabajo: cadenas del día a día, bloques PEM, velocidad, URLs, archivos y tokens.

Esquina de la diversión

Porque una guía completa debería acabar con una sonrisa:

  • Cada PNG Base64 de internet empieza con los mismos caracteres: la cabecera mágica 89 50 4E 47 se codifica como iVBOR, así que hacer grep a un archivo HTML buscando esa huella encuentra cada imagen incrustada. Los formatos tienen huellas, y esta es un prefijo.
  • base64encode("Man") no codifica la palabra Man. Se pone a buscar un archivo llamado Man, avisa de que no puede abrirlo, y se rinde. La trampa más específica de R de todo el ecosistema Base64, escondida a plena vista en la lista de argumentos.
  • Una cadena envolvente de OpenSSL siempre termina con un salto de línea final, así que la última línea de un bloque PEM nunca es la última línea del archivo. El envolvente lleva un punto al final de la frase, quieras tú o no.
  • La cadena vacía se codifica de tres maneras distintas: base64enc devuelve un vector de cero cadenas, b64 y base64url devuelven la cadena vacía. R se topa con la nada, y R da tres respuestas.
  • jose escribe el header JWT con typ antes que alg, mientras la mayoría de los ejemplos escritos a mano ponen alg primero. El JSON no le importa el orden de las claves, y la verificación JWT lo sabe, pero tu diff de cadenas no.
  • El límite de 76 caracteres de MIME es una decisión de 1993 sobre la longitud de línea de los mensajes, llevada a través de cuatro RFCs hasta cada adjunto de email que hayas enviado. El envolvente que estás fijando hoy se discutió antes de que R tuviera un plot a color.
  • b64 decodificará alfabetos que nunca has visto, BinHex, UTF-7 modificado de IMAP, bcrypt, crypt y el par URL-safe, con un motor cada uno. El mismo código Rust que empaqueta tu campo JSON puede desembalar un adjunto de Macintosh de los 80.

El otro lado

Elige tu codificador según el trabajo: base64enc para cadenas del día a día, con linewidth y newline cuando el canal tiene una forma; openssl cuando quieras una salida de 64 caracteres al estilo PEM o ya esté en el proyecto; b64 cuando quieras velocidad, vectores o los motores URL-safe; y los especialistas pequeños base64 y base64url para menudos de archivos y cadenas URL-safe. Convierte con charToRaw() antes de codificar con base64enc::base64encode(), elige el envolvente por canal y no por paquete, guarda el alfabeto URL-safe para lo que viva en una URL o un JWT, firma y verifica tokens con jose, y prueba cada ruta con un viaje de ida y vuelta. El formato en sí no perdona en exactamente dos sitios, el alfabeto y los saltos de línea, y los codificadores difieren sobre todo en qué tan honestamente te avisan cuando te has equivocado en uno de ellos. Y cuando la otra dirección llame - cuando llegue una cadena y necesites desmontarla, comprobar qué significan los bytes, y sobrevivir a los decodificadores que fallan en silencio - el artículo hermano cubre la decodificación Base64 en R en detalle.

Última actualización: 2026-09-08

Artículo relacionado: Decodificación Base64 en R: una guía completa