¿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 Python: una guía completa

Aquí está el otro lado de la moneda. Tienes datos en las manos - un archivo, un par usuario/contraseña, un bulto binario, un párrafo de Unicode - y más adelante en el camino tienen que viajar por un canal que no acepta nada que no sea letras. Ese es todo el trabajo de Base64: reescribir tres bytes de datos como cuatro caracteres de un alfabeto de 64 caracteres, rellenar el grupo final con = para que todo salga de cuatro en cuatro, y entregar las letras. La página de inicio de este sitio explica el formato por completo, alfabeto incluido, así que lo dejaremos en una respiración y dedicaremos el resto del tiempo a lo que Python hace de verdad con ello.

La economía se merece una frase honesta antes de empezar, porque el número aparece en todas las conversaciones sobre esto: el precio de esa seguridad de texto es el tamaño. Base64 expande tus datos un tercio, cuatro caracteres por cada tres bytes de entrada, así que un megabyte binario se convierte en un megabyte y un tercio de letras. Para un token o un valor de configuración eso no es problema; para un archivo de video es la razón por la que deberías plantearte tus opciones.

Y la buena noticia: la respuesta de Python a todo esto es un import y una función. base64.b64encode lleva décadas en la biblioteca estándar, no necesita instalación, y corre a velocidad C por debajo. El resto de esta guía es la larga cola que hace al one-liner útil en el mundo real: la regla de solo bytes que frena la mitad de todos los bugs de TypeError, el alfabeto seguro para URLs, las herramientas de doblado de líneas MIME, y los protocolos - JWTs, cabeceras HTTP, apretón de manos WebSocket, correo, PEM, data URLs - en los que Base64 está haciendo silenciosamente su trabajo.

Conoce a b64encode

El contrato cabe en cuatro frases. Una: la entrada es un objeto tipo bytes - bytes, bytearray, memoryview - y una cadena plana es rechazada. Dos: la salida es un objeto bytes, nunca un str. Tres: la salida siempre se rellena hasta un múltiplo de cuatro caracteres, así que incluso un solo byte de entrada produce Zg==. Cuatro: la salida es una sola línea, nunca doblada, por grande que sea la entrada. Todo lo demás de este artículo es comentario sobre esas cuatro frases:

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

Para conseguir una cadena de verdad, para una URL o una cabecera o un campo JSON, decodifica el resultado como ASCII. El alfabeto garantiza que no puede haber nada más, lo que convierte este paso en seguro y barato:

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

Hay un argumento más en la firma, altchars, e intercambia el + y el / del alfabeto estándar por otro par de caracteres. Ese es exactamente el tornillo que hay detrás de la variante segura para URLs, así que guarda esa idea - la encontrarás dentro de unas secciones cuando hablemos de tokens y cadenas de consulta.

El Muro de Tipos: str No Es bytes

El primer muro específico de Python en este artículo es el sistema de tipos, y vale la pena aprender a sentirlo. b64encode rechaza las cadenas con uno de los mensajes de error menos perdonavidas del lenguaje:

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

La solución es el hábito más importante de toda esta guía: convierte tu texto en bytes primero, y elige la codificación a conciencia en lugar de cruzar los dedos:

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

Mismos caracteres, dos cadenas de bytes diferentes, dos salidas Base64 diferentes. La elección de codificación es una decisión, no un detalle. UTF-8 es el valor por defecto para cualquier cosa que cruce un cable, una base de datos o una API. UTF-16 aparece cuando hablas con APIs de Windows, y trae una marca de orden de bytes al principio que quizá no quieras codificar, y que puedes descartar usando utf-16-le o recortándola con lstrip("\ufeff"). Latin-1 sigue escondiéndose en viejos archivos europeos, donde un carácter es exactamente un byte y la pregunta entera nunca surge. El modelo mental a conservar: el codificador nunca mira tu texto; solo ve bits. En el momento en que los bytes cruzan el muro, la pregunta del charset queda cerrada - que es también por qué el lado del decodificado tiene que preguntar, más tarde, quién poseía ese charset.

base64url: Cambia Dos Letras, Tira el Relleno

El alfabeto estándar esconde dos caracteres que a las URLs y a los sistemas de archivos les dan miedo. El signo + se lee en silencio como espacio en cualquier decodificador de formularios, y el signo / es un separador de ruta, así que un payload del alfabeto estándar en una cadena de consulta o en un nombre de archivo es una bomba de relojería. La sección 5 de RFC 4648 define la solución: una variante donde + se convierte en - y / en _, donde el relleno se descarta cuando la longitud de los datos se conoce por contexto, y que el RFC insiste en llamar base64url y no solo "base64". La encontrarás en JSON Web Tokens, tokens OAuth y parámetros de cursor de APIs, es decir, en la mayor parte de la web moderna.

Python trae tanto una función dedicada como el tornillo altchars de la primera sección, y producen una salida idéntica:

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

En tokens y cadenas de consulta el relleno suele irse también, porque un = final necesitaría codificación por porcentaje y algunos middleboxes lo destrozaran igualmente:

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

Recorta, envía, y el receptor le vuelve a poner los rellenos con el truco del módulo, "=" * (-len(s) % 4), que produce exactamente tantos rellenos como la longitud exige. La regla práctica: si los datos van a estar en una URL, un nombre de archivo o un JWT, usa la variante urlsafe y tira los rellenos; si van a estar en un cuerpo de correo o en un archivo de texto, el alfabeto estándar con su relleno es la norma.

Cuando Tu Lector Quiere Líneas: MIME y la Regla de los 76 Caracteres

La única línea interminable de b64encode es perfecta para campos JSON, cabeceras y URLs, pero el correo tiene opiniones. RFC 2045, el estándar MIME, exige que la salida Base64 se rompa en líneas de como máximo 76 caracteres, y las herramientas heredadas de Python fueron construidas para producir exactamente eso. encodebytes, añadido en Python 3.1, hace el doblado para un objeto de bytes:

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

La mecánica es un poco curiosa. El módulo codifica en trozos de 57 bytes, la constante MAXBINSIZE, porque 57 bytes se convierten en exactamente 76 caracteres, y en el CPython moderno cada línea doblada termina con un simple salto de línea. RFC 2045 pedía CRLF, pero la salida LF de Python es aceptada por cada decodificador del ecosistema, incluida la propia de Python. La función heredada de archivo a archivo, encode, hace el mismo doblado directamente de un descriptor de archivo a otro, lo que la convierte en una herramienta ordenada para archivos grandes que no quieres mantener en memoria dos veces.

Qué herramienta cuándo, en corto: b64encode para cualquier cosa que vaya a un campo JSON, una URL, una cabecera o una columna de base de datos; encodebytes para cuerpos de correo y armaduras estilo PEM; la heredada encode cuando estás transmitiendo un archivo grande y quieres el doblado gratis. Elegir la equivocada es un bug clásico, porque un solo salto de línea perdido dentro de un campo JSON basta para que un decodificador estricto del otro lado lance una excepción.

La Familia Extendida

El módulo base64 es de verdad el módulo base-N, y trae toda la familia de RFC 4648 más un par de parientes de otras esquinas del mundo informático. La mayoría son sustituciones de una línea para el mismo contrato de bytes-entra-bytes-salida:

Funciones Alfabeto Cuándo La Encuentras
b16encode / b16decode 0-9A-F "Base16" es solo hexadecimal; el viaje de ida y vuelta más rápido del módulo, genial para hashes y UUIDs
b32encode / b32decode A-Z2-7 claves de licencia y códigos de activación; sin 0, O, 1 ni I, así que sobrevive a ser leída en voz alta
b32hexencode / b32hexdecode 0-9A-V Base32 con alfabeto hexadecimal, añadido en Python 3.10; mantiene los datos codificados ordenables lexicográficamente
a85encode / a85decode 85 caracteres imprimibles ASCII85 de PostScript y PDF, el descendiente de la utilidad btoa de Unix; en el módulo desde Python 3.4
b85encode / b85decode 85 caracteres imprimibles el formato Base85 usado por git y Mercurial en diffs binarios; también desde Python 3.4
z85encode / z85decode 85 caracteres imprimibles el Z85 de ZeroMQ, añadido en Python 3.13; enmarca los datos en grupos de cuatro bytes

Ninguna de ellas cambia las reglas que ya has aprendido: bytes entra, bytes sale, un alfabeto a elegir, y una función de decodificado parejada esperando al otro lado. En la práctica recurrirás a b16 cuando un humano deba poder leer el valor, a b32 cuando el valor va a ser tecleado o dicho a mano, y a los primos de 85 caracteres solo cuando una especificación te lo diga. Para todo lo demás, el par de Base64 del principio de este artículo es la herramienta correcta, y es la que todo lo demás construye encima.

Imágenes en la Página: Data URLs

El Base64 más visible en la web es el URI data:: medios incrustados directamente en HTML o CSS para que el navegador no dispare una segunda petición. El formato es data:, el tipo de medio, la palabra base64, una coma, y los bytes codificados. Construir uno a partir de un archivo en disco es de tres líneas:

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

Dos advertencias, ambas baratas de observar. Primera, el navegador renderizará un data URI con gusto, y con gusto también los mantendrá en el documento por megabytes: para cualquier cosa más allá de unos pocos kilobytes, una petición de imagen normal con una cabecera de caché correcta gana en cada métrica que importa. Segunda, el tipo de medio después de la coma es una promesa. Si los bytes son un JPEG, el URI dice image/jpeg, porque algunas herramientas validan el par y algunos renderizadores simplemente se niegan a adivinar. El paso .decode("ascii") tampoco es decoración; sin él estás concatenando un objeto bytes a una cadena y te llevas un TypeError, el muro de tipos haciendo su ronda.

Tokens que Puedes Entregar: JWTs

Un JSON Web Token son tres pedazos base64url unidos por puntos: una cabecera, un payload y una firma. Si vas a emitir tokens de verdad, no armes los pedazos a mano. Instala PyJWT (pip install pyjwt) y déjalo construir las partes base64url, el relleno y la firma en una sola llamada:

import jwt
# Una clave de menos de 32 bytes provoca la InsecureKeyLengthWarning de PyJWT, una amonestación razonable para una clave de demostración.
token = jwt.encode(
  {"sub": "1234567890", "name": "John Doe"},
  "super-secret-key",
  algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>

Por debajo, PyJWT hace exactamente lo que describe este artículo: serializar a JSON, pasarlo por el codificador urlsafe, y quitar los rellenos, según la definición de JWS en RFC 7515. Si alguna vez necesitas armar una pieza a mano, para un fixture de pruebas o una sesión de depuración, la receta es la misma aritmética en todas partes:

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

Una nota sobre la dirección de la confianza: construir un token es la mitad fácil. El receptor debe verificar la firma antes de confiar en una sola claim, y PyJWT 2.x no decodificará un token sin una lista explícita de algorithms, lo cual es una característica, porque el error de "cualquier algoritmo" es una de las líneas de código de autenticación más caras jamás escritas.

HTTP: Autenticación Basic y el Apretón de Manos WebSocket

Dos momentos de HTTP viven o mueren con Base64. El primero es el esquema de autenticación más antiguo del protocolo: Basic auth (RFC 7617), donde el cliente envía user:pass, codificado en base64, detrás de la palabra Basic:

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

Si requests ya está en tu stack, construye esta cabecera por ti con auth=("jane", "pa:ss"), que vale la pena usar porque mantiene el detalle de codificación fuera de tu código. Y sé honesto sobre lo que está pasando mientras estás ahí: RFC 7617 es claro en que el esquema "no es un método seguro de autenticación de usuarios, ni protege de ninguna forma a la entidad, que se transmite en texto plano". Las credenciales son recuperables en una línea de código por cualquiera que vea el tráfico, así que esto es una comodidad para conexiones protegidas con TLS, no una frontera de seguridad.

El segundo momento es el apretón de manos WebSocket (RFC 6455), donde el servidor demuestra que leyó la clave aleatoria del cliente respondiendo con el Base64 de un hash SHA-1 de la clave pegada a un GUID mágico:

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

La salida es el valor exacto del ejemplo resuelto del propio RFC, que es una manera encantadora de comprobar una implementación desde cero. En producción, la biblioteca websockets hace este paso por ti en ambos extremos; solo lo artesanas cuando escribes el diminuto servidor de pruebas que demuestra tu comprensión.

Correo: el Cliente Original

Base64 se estandarizó en 1993 para un trabajo, y ese trabajo era el correo: hacer que el binario sobreviviera al mundo solo de texto de SMTP, según Content-Transfer-Encoding: base64 de RFC 2045. El paquete email de Python construye el mensaje, elige la codificación y dobla el cuerpo a la longitud de línea estándar sin que tú escribas una línea de Base64:

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

La parte MIMEApplication es la interesante: envuelve los bytes en las correctas líneas de 76 caracteres y estampa la cabecera de codificación de transferencia, que es exactamente el comportamiento de encodebytes de antes aplicado por el framework. Si estás montando un fragmento suelto en lugar de un mensaje completo, email.encoders.encode_base64(obj) hace una sola codificación-y-doblado directamente sobre un objeto de mensaje. Y cuando el mensaje llega al otro extremo, el lado de la historia del decodificado, desde get_payload(decode=True) hasta el decodificado de cabeceras, está cubierto en el artículo relacionado de decodificado.

Armadura PEM para Claves y Certificados

Los archivos PEM - los certificados, claves privadas y CRLs que empiezan con -----BEGIN ...----- - no son nada más que armadura alrededor de Base64: una línea de etiqueta, Base64 doblado, una etiqueta de cierre. La armadura es fácil de ver a través, porque el cuerpo es solo la salida doblada que ya conoces:

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

Quita las dos líneas de etiqueta, une el resto, y b64decode te devuelve los bytes DER. En producción casi nunca harás esto a mano: el paquete cryptography (pip install cryptography) genera la armadura con public_bytes y la analiza con load_pem_x509_certificate y compañía, haciendo el paso de Base64 por debajo. El camino manual se gana la vida en el momento específico en que los bytes DER crudos ya están en tus manos - una columna de base de datos, un archivo de configuración, un búfer de un protocolo - y la especificación que tienes delante dice "PEM, por favor".

Enviar Archivos: Subidas, Descargas y el Hábito .b64

El caso de uso más antiguo de internet es un archivo binario que tiene que cruzar un canal que solo transporta texto: un FTP que destroza los saltos de línea, un formulario que se niega a las subidas, una ventana de chat que se traga el binario. La receta es leer, codificar, enviar, y dejar que el extremo lejano decodifique, y la mitad interesante son los dos pasos del medio:

import base64
with open("photo.png", "rb") as src:
  data = src.read()
wrapped = base64.encodebytes(data)
with open("photo.b64", "wb") as dst:
  dst.write(wrapped)
print(len(wrapped), "bytes on disk for", len(data), "in the photo")
# un tercio más grande que el original

Dos notas. La extensión .b64 es una convención de la comunidad, no un estándar, así que el lado receptor también tiene que conocer la convención - por eso las APIs JSON suelen envolver el payload en un campo con nombre como "image_base64" y lo dicen en su documentación. Y las líneas dobladas de 76 caracteres de encodebytes son el formato de elección para el archivo en disco, porque se copian y pegan limpiamente por todas las herramientas de texto que la gente tiene, desde clientes de correo hasta lectores de PDF. La dirección inversa, leer de nuevo tal archivo a bytes, es una sola llamada en el artículo relacionado de decodificado; aquí solo eres el remitente, y el trabajo del remitente es ser constante.

La Pregunta del Almacenamiento: Archivos de Configuración, Variables de Entorno y Bases de Datos

A los desarrolladores les encanta meter Base64 en sitios que solo aceptan texto: un archivo .env, un ajuste .ini, una columna TEXT. El paso de codificación es trivial, y la forma más común es JSON dentro de Base64:

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

Y entonces la advertencia, porque aquí vive el malentendido más caro de todo este artículo. Base64 no es una ofuscación que resista, y no es cifrado. La sección 12 de RFC 4648 lo pone tan claro como un RFC puede: la codificación base "oculta visualmente información que de otro modo se reconocería fácilmente, como contraseñas, pero no proporciona ninguna confidencialidad computacional". Un archivo .env con secretos en Base64 te protege de la persona que echa un vistazo, no de la persona que lo lee, y un comando de base64 -d después el "secreto" está sentado en su terminal en texto plano. Si los datos son de verdad sensibles, cifralos primero - el paquete cryptography trae Fernet exactamente para esto - y solo entonces codifica en Base64 el texto cifrado si tu almacenamiento exige texto.

Un Millón de Bytes Después: Datos Grandes y Troceado

b64encode es una función a velocidad C - en un portátil típico procesa un megabyte en aproximadamente un milisegundo - pero no es una función de flujo. No existe ningún par de actualizar-y-terminar en toda la biblioteca estándar, así que codificar datos más grandes de los que quieres mantener en memoria significa hacer la aritmética de límites por tu cuenta. Tres bytes de entrada hacen cuatro caracteres de salida, así que cualquier frontera de trozo tiene que caer en una costura de tres bytes:

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

La salida es idéntica byte a byte a codificar el archivo entero en una sola llamada, porque la costura de tres bytes es el único sitio donde el agrupamiento puede romperse. El relleno aparece exactamente una vez, en el trozo final, que es lo que un decodificador estricto del otro lado esperará. La línea iter(lambda: handle.read(65536), b"") es el idiomatismo estándar para leer un archivo en trozos de tamaño fijo, y la variable leftover es el algoritmo entero. El lado del decodificado conserva una costura de cuatro caracteres en lugar de una de tres bytes, así que los dos artículos se reparten la aritmética entre ellos en lugar de repetirla.

Dónde Fallan los Codificadores

El lado de la codificación tiene menos trampas que el de la decodificación, porque hay menos que pueda salir mal cuando eres tú quien produce las letras. Aun así, estas aparecen cada semana, y cada una tiene una solución de dos minutos si la reconoces pronto:

  • Alimentar al codificador con una cadena. El TypeError de la sección del muro de tipos. Arregla la fuente con .encode("utf-8"), y piensa en qué charset quieres de verdad antes de teclearlo.
  • Olvidar que la salida son bytes. b64encode devuelve bytes; el str es lo que va a una URL o a un campo JSON, así que el paso .decode("ascii") es parte de la receta, no un pensamiento posterior.
  • Armar el intercambio urlsafe a mano. str.replace("+", "-").replace("/", "_") funciona, pero son dos letras de deuda de mantenimiento donde urlsafe_b64encode es una sola llamada. Peor, un intercambio a medio hacer, los pluses arreglados y las barras olvidadas, produce un alfabeto que no coincide con ninguna especificación.
  • Dejar los rellenos en una URL. Un = final dentro de una cadena de consulta es codificado por porcentaje por una herramienta y quitado por otra, y la matemática de relleno del receptor se rompe de la manera más confusa. Quítalos; la longitud le dice al decodificador todo lo que necesita.
  • Doblar donde no se quiere. Los saltos de línea de encodebytes son correctos para correo y PEM, y veneno para un campo JSON o una URL. Un solo salto de línea perdido basta para que un decodificador estricto del otro lado lance una excepción sobre tus datos, no sobre tu formato.
  • Codificar dos veces. Los datos ya eran Base64 aguas arriba - un campo que llega precodificado de otra API, un archivo al que le hicieron el tratamiento .b64 dos veces - y la segunda pasada produce una cadena que se decodifica de vuelta a la primera codificación. Haz la ida y vuelta una vez, comprueba los bytes mágicos, y para.
  • Confiar en Base64 con secretos. La advertencia de la sección de almacenamiento, repetida porque cuesta dinero de verdad: si el modelo de amenazas incluye a alguien leyendo el archivo, necesitas un cifrado, no un alfabeto.

Un Changelog que Puedes Leer de Verdad

La edad del módulo se nota en mejoras silenciosas y con fecha, no en revoluciones. La versión corta, en el orden en que llegaron las piezas, desde el punto de vista del codificador:

Versión Qué Pasó
Python 2.4 (2004) llega el soporte completo de RFC 3548 de Barry Warsaw: las familias b16, b32 y b64, más las variantes standard_* y urlsafe_* que se usan hoy
Python 3.1 (2009) llega encodebytes y encodestring queda deprecada, un cambio de nombre con el que los viejos tutoriales siguen tropezando
Python 3.4 (2014) cada codificador acepta cualquier objeto tipo bytes, y a85encode y b85encode se unen al módulo
Python 3.6 (2016) binascii.b2a_base64 aprende un interruptor newline, que es lo que permite que b64encode siga siendo una línea interminable
Python 3.9 (2020) los nombres heredados encodestring y decodestring son por fin eliminados
Python 3.10 (2021) b32hexencode y b32hexdecode, los primos del alfabeto hexadecimal ordenable
Python 3.13 (2024) z85encode y z85decode, el alfabeto de ZeroMQ, se unen a la familia
Python 3.14 (2025) importaciones más rápidas en toda la biblioteca estándar, base64 incluido, y un b16decode hasta seis veces más rápido, porque su validación ahora corre sobre bytes.translate en lugar de una expresión regular

El hilo conductor, si lo quieres: el módulo fue reescrito en 1995 para delegar su trabajo al módulo binascii a nivel C, y esa delegación sigue siendo cierta hoy. El primer cambio de la era de los bytes, un commit de 2007 durante el desarrollo de Python 3 que hizo que todo usara bytes en todas partes, es de donde viene el muro de tipos de este artículo, y es la razón por la que un codificador moderno toma bytes y devuelve bytes, con todo lo demás un envoltorio alrededor de ese único contrato.

Lo que el Módulo No Te Cuenta

El trabajo serio está hecho, así que aquí van las pequeñas delicias del lado de la codificación del libro de cuentas:

  • El propio ejemplo de la documentación lleva más de una década con la misma demostración: entra b'data to be encoded', sale b'ZGF0YSB0byBiZSBlbmNvZGVk'. Ya has conocido a este par, lo sepas o no.
  • La función C bajo b64encode añade su salto de línea final con un comentario que dice "Añadir un salto de línea de cortesía". Una cultura entera, en una línea de fuente.
  • La palabra password se codifica como cGFzc3dvcmQ=, por eso el Base64 en un archivo de log parece un secreto para un analizador y está a un comando de serlo para un lector.
  • b64encode nunca dobla. Jamás. Un gigabyte de entrada produce una sola línea de 1,3 gigabytes, y la función ni parpadea. Si querías líneas, tenías que pedir encodebytes.
  • La docstring del módulo sigue citando RFC 3548, la edición de 2003 de la especificación. RFC 4648 tomó el relevo en 2006; la docstring simplemente nunca se enteró.
  • Python 2 no tenía ningún muro de tipos: b64encode aceptaba un str con gusto y devolvía uno. La gran reforma de bytes de 2007 terminó con eso, y los viejos tutoriales de Python 2 son donde la mayoría de los hilos de "por qué se me cae mi codificado" siguen señalando.
  • z85encode, el miembro más nuevo de la familia (Python 3.13), es el más exigente: ZeroMQ enmarca los datos en grupos de cuatro bytes, así que la especificación exige que la salida codificada sea un múltiplo de cinco caracteres - y la documentación te deja el relleno a ti: la entrada debe llegar como múltiplo de 4 bytes (el codificador no la rellena por ti; una entrada de 3 bytes produce un marco de 4 caracteres que ningún par ZeroMQ aceptará).

Así que la filosofía del codificador en tres reglas. Decide los bytes primero y la codificación después, porque el muro de tipos es donde nace la mayor parte de los bugs de Base64 en Python. Elige el alfabeto para el canal, no para los datos: estándar con rellenos para correo y archivos, base64url sin rellenos para URLs y tokens, y nunca improvises una tercera variante en el teclado. Y mantén la salida en la forma que su lector espera, una línea para JSON y cabeceras, líneas de 76 caracteres para MIME y PEM, porque el decodificador del otro lado te lo hará cumplir.

Cuando esas letras llegan al otro extremo, es donde la cosa se pone interesante: rellenos faltantes, descartes en silencio, payloads que no son del todo Base64, y un decodificador con dos estados de ánimo que navegar. Todo eso está cubierto en detalle en el artículo relacionado de decodificado Base64 al final de esta página, y las dos guías se leen bien como pareja. Feliz codificado.

Última actualización: 2026-09-08

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