Codificar y decodificar Base64 en Python: b64encode, urlsafe y la regla de bytes
El módulo `base64` de Python es correcto, completo y se usa mal de forma rutinaria. Toda la API es `bytes entran, bytes salen`. El `str` que quieres al final está a una llamada de decode de distancia.
b64encode y b64decode solo aceptan bytes
base64.b64encode recibe bytes y devuelve bytes. Si le pasas un str te suelta TypeError. El round-trip completo de texto a string Base64 y vuelta es así:
```py import base64
text = "AldeaCode" encoded = base64.b64encode(text.encode("utf-8")).decode("ascii") # "QWxkZWFDb2Rl"
decoded = base64.b64decode(encoded).decode("utf-8") # "AldeaCode" ```
Las dos llamadas a encode/decode flanquean el trabajo Base64. Olvidarlas es el bug Base64 más frecuente en Python en Stack Overflow, de lejos.
urlsafe es lo que necesitan los tokens
Base64 estándar usa + y / en su alfabeto. Los dos son caracteres reservados en URLs y obligan a percent-encoding en cuanto intentas meterlos en un query param o un path.
base64.urlsafe_b64encode cambia + por - y / por _, que es lo que el RFC 4648 §5 llama "Base 64 Encoding with URL and Filename Safe Alphabet". Los JWT y la mayoría de formatos modernos de token esperan la variante URL-safe, normalmente sin padding.
def b64url_sin_padding(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
Al decodificar de vuelta tienes que rellenar a un múltiplo de 4 con = antes de que urlsafe_b64decode lo acepte.
Archivos y datos binarios viajan limpios
Base64 se diseñó para meter bytes arbitrarios por transportes de 7 bits (email, JSON, atributos XML). Codificar un PNG en un data URI es uno de sus usos canónicos:
```py with open("logo.png", "rb") as f: data = f.read()
data_uri = "data:image/png;base64," + base64.b64encode(data).decode("ascii") ```
El overhead del 33% es intrínseco al formato: 3 bytes crudos se vuelven 4 caracteres ASCII. Si estás mandando imágenes en Base64 por la red para ahorrar peticiones HTTP, mide antes. Para imágenes de más de unos pocos KB, una respuesta binaria normal casi siempre va más rápido.
Padding y validación
Los strings Base64 sin padding técnicamente no cumplen RFC 4648, pero la convención está tan extendida (JWT, tokens OAuth, JOSE) que vas a encontrarte strings sin padding constantemente. b64decode acepta validate=True, que rechaza caracteres fuera del alfabeto Base64, justo lo que quieres para entrada de usuario.
Para entradas sin padding, rellena tú mismo: s + "=" * (-len(s) % 4) redondea al siguiente múltiplo de 4. Más limpio que ir capturando el binascii.Error de la librería.
Ejemplo completo
pythonimport base64
# Round-trip Base64 estándar
text = "AldeaCode 🛠"
encoded = base64.b64encode(text.encode("utf-8")).decode("ascii")
decoded = base64.b64decode(encoded).decode("utf-8")
assert decoded == text
# URL-safe, sin padding (estilo JWT)
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def b64url_decode(s: str) -> bytes:
padded = s + "=" * (-len(s) % 4)
return base64.urlsafe_b64decode(padded) ¿Solo necesitas el resultado?
Cuando solo quieres inspeccionar un trozo de Base64 que has sacado de un JWT o de una respuesta de curl, abrir una shell de Python sobra. Pégalo en la herramienta Base64 del navegador y lee el texto y los bytes decodificados al instante, con la variante URL-safe a un click.
Abrir Codificador y Decodificador Base64 →Preguntas frecuentes
¿Por qué mi string Base64 lleva un salto de línea al final?
Si usaste base64.encodebytes (con la s) añade un newline cada 76 caracteres y otro al final, por convención MIME. Usa b64encode (sin la s) para una sola línea limpia.
¿Puedo codificar emoji en Base64 sin problema?
Sí. Codifica el string a UTF-8 primero para obtener bytes y luego b64encode. La decodificación va al revés: b64decode y después .decode('utf-8'). Si te saltas el paso UTF-8 destrozas todo lo que esté fuera de ASCII.
¿Base64 es seguro?
No. Base64 es codificación, no cifrado. Cualquiera que lea el string puede decodificarlo. Úsalo para transporte, nunca para esconder secretos, y nunca en lugar de un hash o HMAC.