Python base64: Encode Binary Data

Published on: August 18, 2026
Reading time: 5 minutes
A person in a hoodie coding on dual monitors, depicting cybersecurity and hacking themes.

The Python base64 module converts binary bytes into printable ASCII and decodes those representations back to bytes. This allows images, identifiers, keys, and small blobs to travel through text-oriented formats such as JSON, configuration files, headers, and some URL components.

Base64 is an encoding, not encryption. Anyone who can read the string can recover the original bytes. It does not authenticate the sender or reliably protect integrity. Use encryption for confidentiality and authenticated hashes or signatures for authenticity.

Basic encoding and decoding

import base64

data = b"binary message\x00\xff"
encoded = base64.b64encode(data)
restored = base64.b64decode(encoded)

assert restored == data
print(encoded)

Encoding functions accept bytes-like objects and return ASCII bytes. When storing the result in JSON, convert at the boundary:

text = base64.b64encode(data).decode("ascii")
original = base64.b64decode(text)

Base64 increases size

Three source bytes become four text characters, creating roughly 33 percent overhead before surrounding JSON or protocol syntax. Base64 is not compression. If the transport requires text and size matters, compress appropriate data first with a format such as Python zlib, then encode the compressed bytes.

Strict decoding with validate

By default, b64decode() discards characters outside the alphabet before checking padding. This can accommodate wrapped MIME text, but it may accept unexpected garbage. For API input and tokens, enable strict validation:

import base64
import binascii

try:
    data = base64.b64decode(value, validate=True)
except (binascii.Error, ValueError) as exc:
    raise ValueError("invalid Base64") from exc

Also limit input and output lengths. A very large encoded string can exhaust memory before its decoded contents are inspected.

Padding

The = character completes the final four-character group. Some URL-oriented protocols remove padding. Restore only what is necessary and document the rule:

import base64

def decode_unpadded(text: str) -> bytes:
    if len(text) > 10_000:
        raise ValueError("input is too long")
    padding = "=" * (-len(text) % 4)
    return base64.urlsafe_b64decode(text + padding)

Do not add arbitrary padding without validating the alphabet and allowed length. A protocol should state whether padding is required, optional, or forbidden.

URL-safe Base64

urlsafe_b64encode() substitutes - for + and _ for /:

import base64

identifier = base64.urlsafe_b64encode(b"file/2026+version")
print(identifier)
print(base64.urlsafe_b64decode(identifier))

The output may still contain =. URL-safe describes the alphabet, not automatic suitability for every path or query context. Build complete URLs with Python urllib.parse.

Alternative alphabets

b64encode() accepts exactly two replacement bytes for + and /:

import base64

custom = base64.b64encode(b"data", altchars=b"-_")
original = base64.b64decode(custom, altchars=b"-_", validate=True)

Use the dedicated URL-safe helpers for the standard URL alphabet. Custom alphabets reduce interoperability and must be documented on both sides.

Binary files in JSON

import base64
import json
from pathlib import Path

path = Path("icon.png")
content = path.read_bytes()

if len(content) > 2 * 1024 * 1024:
    raise ValueError("file exceeds the limit")

payload = json.dumps({
    "name": path.name,
    "content_base64": base64.b64encode(content).decode("ascii"),
})

For large files, prefer binary upload, multipart requests, or object storage. Base64 increases bandwidth and often keeps original bytes, encoded text, and decoded bytes in memory simultaneously.

Data URLs

import base64

data = b"..."
media_type = "image/png"
data_url = f"data:{media_type};base64,{base64.b64encode(data).decode('ascii')}"

Do not trust the declared media type. Validate the actual format and apply an allowlist. Data URLs can carry HTML, SVG, or scripts and may create XSS risks when inserted into a page without a restrictive policy.

Base32

Base32 uses a smaller, often more human-friendly alphabet:

import base64

code = base64.b32encode(b"temporary secret")
print(code)
print(base64.b32decode(code))

b32decode() rejects lowercase by default. casefold=True accepts it. The map01 option can map visually similar digits, but the secure default is to reject those substitutions and avoid ambiguity.

Base32 Hex

b32hexencode() and b32hexdecode() use the extended hexadecimal alphabet from RFC 4648. Digits 0 and 1 are real alphabet members and are not interchangeable with letters.

Base16

import base64

hex_value = base64.b16encode(b"ABC")
print(hex_value)
print(base64.b16decode(hex_value))

Decoding accepts uppercase by default. Enable casefold only if the protocol allows lowercase. For ordinary hexadecimal identifiers, bytes.hex() and bytes.fromhex() may be more direct.

Ascii85, Base85, and Z85

Base85 families represent four bytes with five characters and have less overhead than Base64. Python exposes several incompatible variants:

  • a85encode(): Ascii85 for PostScript and PDF-related formats.
  • b85encode(): the alphabet used by tools such as Git.
  • z85encode(): ZeroMQ Z85, available since Python 3.13.
import base64

data = b"12345678"
print(base64.a85encode(data))
print(base64.b85encode(data))
print(base64.z85encode(data))

They are not interchangeable. Follow the external specification for markers, padding, whitespace, and alphabet.

Z85 length rules

Z85 requires input length to be a multiple of four and encoded length to be a multiple of five. If an application adds padding, it must also preserve the original length or define an unambiguous removal rule.

Legacy MIME interface

encodebytes() inserts line breaks every 76 characters according to MIME. For complete email messages, use the email package, which manages headers, transfer encodings, and multipart structure. The modern functions are better for APIs and application storage.

HTTP Basic Authentication

Basic Authentication encodes username:password with Base64. The encoding does not protect the credentials; TLS provides transport confidentiality.

import base64

credentials = "user:password".encode("utf-8")
header = "Basic " + base64.b64encode(credentials).decode("ascii")

Never log the header, avoid long-lived shared passwords, and prefer short-lived credentials or stronger mechanisms when possible.

Never store passwords as Base64

Base64-encoded passwords are equivalent to plaintext. Password storage requires a password-hashing algorithm with a salt and configurable cost. Private keys and tokens belong in encrypted storage or a secret manager.

Signed tokens

JWT and similar formats use Base64URL to represent sections, but their security comes from a signature or MAC. Decoding a token does not verify it. A complete verifier must enforce the algorithm, key, signature, issuer, audience, expiry, and other claims.

Bound decoding

import base64
import binascii

MAX_TEXT = 4 * 1024 * 1024
MAX_OUTPUT = 3 * 1024 * 1024

def decode_limited(text: str) -> bytes:
    if len(text) > MAX_TEXT:
        raise ValueError("Base64 input exceeds the limit")
    try:
        data = base64.b64decode(text, validate=True)
    except binascii.Error as exc:
        raise ValueError("invalid Base64") from exc
    if len(data) > MAX_OUTPUT:
        raise ValueError("decoded output exceeds the limit")
    return data

The encoded-to-decoded ratio helps estimate a limit, but the decoded object must still be validated according to its claimed type and structure.

Constant-time comparison

When decoded bytes represent an expected MAC or signature value, compare with hmac.compare_digest() after validating length and format. This reduces timing differences from an ordinary equality comparison.

Structured binary payloads

Base64 transports bytes but does not define their layout. Use Python struct for a versioned binary header with magic bytes and bounded lengths. Batch decoding can use Python queue to apply backpressure.

Testing strategy

Test empty input, every length modulo four, missing and excessive padding, invalid characters, non-ASCII text, standard and URL-safe alphabets, input limits, ambiguous Base32 characters, Base85 padding, random-data round trips, and RFC 4648 test vectors.

Best practices

  • Keep bytes internally.
  • Convert to ASCII only at a boundary.
  • Use validate=True for external input.
  • Set limits before and after decoding.
  • Select the alphabet from a specification.
  • Never confuse encoding with security.
  • Do not log credentials or tokens.
  • Validate decoded binary content.

Conclusion

Python base64 provides Base16, Base32, Base64, Ascii85, Base85, and Z85 for transporting bytes through text channels. The API is simple, but reliable systems need strict validation, explicit limits, and a clear protocol.

Read the official base64 documentation and RFC 4648. Encoding solves representation; confidentiality and authenticity require dedicated security tools.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Close-up of a woman gently holding a Burmese python, showcasing exotic pet care.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python urllib.parse: Handle URLs

    Learn Python urllib.parse to split URLs, build queries, encode components, and avoid risks involving urljoin, redirects, logs, and SSRF.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ipaddress: IPv4 and IPv6

    Learn Python ipaddress to validate IPv4 and IPv6, calculate CIDR networks, split subnets, summarize ranges, and build safer access policies.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A vibrant array of colored thread spools neatly organized in rows, perfect for sewing enthusiasts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python queue: Coordinate Threads

    Learn Python queue to coordinate threads with FIFO, priority, backpressure, task tracking, retries, and graceful shutdown.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Extreme close-up of computer code displaying various programming terms and elements.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python struct: Pack Binary Data

    Learn Python struct to pack binary values, define endianness, reuse buffers, parse records, and validate external protocols safely.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tarfile: Create Safe TARs

    Learn Python tarfile to create compressed TARs, inspect members, and extract archives with filters, limits, and path traversal protection.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python gzip: Compress .gz Files

    Learn Python gzip to read and write .gz files, produce reproducible streams, process large data, and limit external expansion.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026