Home Tutorials Standard Library

Standard Library

Python's base64 Module: Encoding Binary Data as Text

Pyford Notes July 6, 2026 6 min read
Key points
  • Base64 exists because many text-based formats — JSON, email, URLs — can't safely carry arbitrary raw bytes, so binary data gets re-encoded into a restricted set of printable ASCII characters first.
  • Encoding always expands the data by roughly a third (4 output characters for every 3 input bytes), since it trades information density for safe transport through text-only channels.
  • urlsafe_b64encode() swaps two characters that have special meaning inside a URL for two that don't.
  • Base64 is an encoding, not a cipher — anyone can decode it with a single function call, so it provides zero confidentiality and should never be treated as if it hides a secret.

Why encode binary data as text at all

A lot of the infrastructure text passes through — JSON strings, email bodies, URL query parameters — is only designed to safely carry printable text, not arbitrary binary bytes. A raw null byte or an unpaired UTF-8 continuation byte can break a JSON parser, get mangled by an email transport that assumes 7-bit ASCII, or need awkward escaping in a URL. Base64 sidesteps all of that by re-representing binary data using only 64 characters guaranteed to be safe everywhere: uppercase and lowercase letters, digits, and two more symbols.

import base64

data = b"\x00\x01\xfe\xff binary data here"
encoded = base64.b64encode(data)
print(encoded)   # b'AAH+/yBiaW5hcnkgZGF0YSBoZXJl'

b64encode() and b64decode(): the round trip

The encode/decode pair is a straightforward, lossless round trip:

original = b"Hello, world!"
encoded = base64.b64encode(original)
decoded = base64.b64decode(encoded)
print(decoded == original)   # True

Both functions work on bytes, not str — encoding a Python string first requires encoding it to bytes (commonly UTF-8), and the base64-encoded output, while itself made of printable ASCII characters, is still a bytes object rather than a str until it's explicitly decoded with ASCII decoding. Forgetting this distinction is the most common source of a bytes/str TypeError when working with this module for the first time.

The size increase is predictable: base64 uses 4 output characters to represent every 3 input bytes, so encoded data is consistently about 33% larger than the original — worth accounting for when estimating the size of, say, a JSON payload that embeds a base64-encoded image.

The URL-safe alphabet

Standard base64 uses + and / as two of its 64 characters, both of which have reserved meaning inside a URL (+ often means a space in query strings; / is a path separator). Embedding standard base64 output directly into a URL without further escaping risks corrupting it:

base64.urlsafe_b64encode(data)   # uses '-' and '_' instead of '+' and '/'

This is the encoding used, for example, in the header and payload segments of a JWT (JSON Web Token), where the encoded segments need to sit directly inside a URL or an HTTP header value without additional percent-encoding.

Padding characters and why they matter

Because base64 groups input into 3-byte chunks, data whose length isn't a multiple of 3 needs padding to complete the final group — shown as one or two trailing equals-sign characters:

base64.b64encode(b"a")    # b'YQ=='
base64.b64encode(b"ab")   # b'YWI='
base64.b64encode(b"abc")  # b'YWJj'  -- no padding needed

Stripping the padding (some systems, including JWTs, do this deliberately to save a couple of bytes) means the decoder has to know how much padding to add back based on the string's length before decoding will succeed — a subtlety worth knowing about if base64 data from another system fails to decode with a padding error, since the fix is almost always re-adding the missing padding characters rather than anything wrong with the underlying bytes.

Base64 is not encryption

This is worth stating plainly because it's a genuinely common misunderstanding: base64 provides zero confidentiality. It is a public, well-known, reversible transformation — anyone with the encoded string can decode it in one line, with no key required, the same way base64.b64decode() just did above. Storing a password or API key as "base64-encoded" in a config file or database is not meaningfully different from storing it in plain text; it only stops someone from reading it by eye at a glance. Actual secrecy needs real cryptography — the secrets module for generating unguessable tokens, and a proper encryption library for confidentiality, not an encoding scheme whose entire purpose is to be effortlessly reversible by design.