Encoding and Decoding Base64 in Python: Complete Binary-to-Text Encoding Implementation Guide

Encoding and Decoding Base64 in python

Encoding and Decoding Base64 in python

The first time I needed base64 seriously was embedding a small image directly inside an HTML file so I didn’t have to manage a separate asset — no external file, just a long string sitting inline in the <img> tag’s src attribute. That’s a classic use case, but base64 shows up everywhere in real software: email attachments, API payloads, authentication headers, and data URIs. Here’s the complete picture of how it works and how to use it properly in Python.

What Base64 Actually Is

Base64 is a way of representing arbitrary binary data using only 64 printable ASCII characters (A-Z, a-z, 0-9, +, /, plus = for padding). It exists because many systems — email protocols, certain text-based transport layers, JSON payloads — are designed to safely carry text but can mangle raw binary bytes (null bytes, non-printable control characters, or byte sequences that look like protocol control codes).

Base64 solves this by converting binary data into a restricted alphabet that survives transport through text-oriented systems unchanged.

The Core Mechanism: 3 Bytes Become 4 Characters

Base64 works in chunks of 3 bytes (24 bits) at a time, splitting those 24 bits into four groups of 6 bits each. Since 6 bits can represent 64 possible values (2^6 = 64), each 6-bit group maps directly to one character in the base64 alphabet.

Input (3 bytes):  01001101 01100001 01101110
Regrouped (4x6):  010011 010110 000101 101110
Base64 chars:       T      W      F      u

If the input length isn’t a multiple of 3 bytes, padding characters (=) are added to indicate how many “extra” bytes were padded to complete the final group.

Basic Encoding and Decoding in Python

Python’s standard library ships the base64 module, so there’s no need for external dependencies.

import base64

data = b"Hello, World!"

encoded = base64.b64encode(data)
print(encoded)               # b'SGVsbG8sIFdvcmxkIQ=='

decoded = base64.b64decode(encoded)
print(decoded)                # b'Hello, World!'

Notice that base64.b64encode() takes bytes and returns bytes — not str. This trips up beginners constantly because base64-encoded data is conceptually text, but Python is strict about the boundary between bytes and str.

text_data = "Hello, World!"
encoded = base64.b64encode(text_data.encode("utf-8"))
print(encoded.decode("ascii"))  # 'SGVsbG8sIFdvcmxkIQ==' as a real str

I always explicitly encode my starting string to bytes with .encode("utf-8") and decode the base64 output back to a str with .decode("ascii") when I need to embed it in JSON, a URL, or an HTML attribute.

Encoding Files (Images, PDFs, etc.)

import base64

with open("photo.jpg", "rb") as f:
    encoded = base64.b64encode(f.read())

with open("photo_base64.txt", "w") as f:
    f.write(encoded.decode("ascii"))

And to reverse this, reconstructing the original binary file:

import base64

with open("photo_base64.txt", "r") as f:
    encoded = f.read()

decoded_bytes = base64.b64decode(encoded)

with open("photo_reconstructed.jpg", "wb") as f:
    f.write(decoded_bytes)

This is exactly how data URIs work in HTML/CSS:

import base64

with open("icon.png", "rb") as f:
    encoded = base64.b64encode(f.read()).decode("ascii")

data_uri = f"data:image/png;base64,{encoded}"
print(f'<img src="{data_uri}">')

URL-Safe Base64

The standard base64 alphabet uses + and /, both of which have special meaning in URLs (+ often represents a space, / is a path separator). For anything going into a URL — like a token in a query string — I use the URL-safe variant instead.

import base64

data = b"some binary token data \xff\xfe"

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

decoded = base64.urlsafe_b64decode(url_safe_encoded)
print(decoded == data)  # True

Internally, urlsafe_b64encode() runs the standard base64 encoding and then simply translates + to - and / to _ — it’s the same algorithm with a different final character substitution.

Other Encoding Variants in the base64 Module

Python’s base64 module actually supports several related encodings beyond standard base64:

import base64

data = b"Hello, World!"

print(base64.b32encode(data))   # Base32 — uses A-Z and 2-7
print(base64.b16encode(data))   # Base16 — equivalent to hex encoding
print(base64.a85encode(data))   # Ascii85 — more space-efficient than base64

base64.b16encode() is functionally identical to hex encoding (data.hex()), just returned in a slightly different format (uppercase bytes rather than a lowercase string). Base32 is sometimes preferred for case-insensitive contexts (like reading codes aloud or entering them manually), and Ascii85 packs more information per character, producing shorter output, though it’s less universally supported by other tools and languages.

How Base64 Affects Data Size

Because 3 bytes become 4 characters, base64-encoded data is always roughly 4/3 the size of the original binary data — about a 33% overhead. This matters when you’re embedding large files, sending big payloads over an API, or storing base64 strings in a database — the size increase is real and should factor into your design decisions, especially for large binary blobs like images or videos.

import base64

original = b"x" * 3000
encoded = base64.b64encode(original)

print(len(original))  # 3000
print(len(encoded))    # 4000 — exactly the expected 4/3 ratio

Handling Padding and Malformed Input

Base64 decoding is strict about length — the input must be a valid multiple of 4 characters (with correct padding), or decoding raises an error.

import base64

try:
    base64.b64decode("SGVsbG8")  # missing padding
except Exception as e:
    print(f"Decode failed: {e}")

You can pass validate=False (the default) to be lenient about non-alphabet characters, but missing padding still typically needs to be handled manually if you’re working with data from a source that strips it (some JWT-related contexts do this).

def fix_padding(s):
    return s + "=" * (-len(s) % 4)

fixed = fix_padding("SGVsbG8")
print(base64.b64decode(fixed))  # b'Hello'

Real-World Applications

import base64

credentials = "myuser:mypassword"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("ascii")
print(f"Authorization: Basic {encoded_credentials}")

Common Mistakes

Confusing base64 with encryption. Base64 provides zero confidentiality — anyone can decode it instantly. I’ve seen people store “encoded” passwords in base64 thinking it’s secure; it absolutely is not.

Mixing up bytes and str. Forgetting to .encode() before encoding, or .decode() after decoding, is probably the single most common base64 bug in Python — you’ll get a TypeError telling you a str was expected but bytes was given, or vice versa.

Using standard base64 in URLs and hitting broken links or truncated query parameters because of unescaped + and / characters — always use urlsafe_b64encode() for anything going into a URL.

Assuming base64 output is always shorter than the input. It’s always larger (about 33% larger) — this is the opposite of compression, and conflating the two is a common misunderstanding.

Debugging Tips

Performance Considerations

FAQs

Is base64 the same as encryption or hashing? No. Base64 is purely an encoding scheme for representing binary data as text — it’s fully reversible with no key required, and provides no security whatsoever on its own.

Why does my base64 string end with one or two = signs? Padding characters indicate that the final group of 3 input bytes was incomplete (1 or 2 bytes instead of 3), and they let the decoder know how many bits to discard from the final decoded group.

What’s the difference between b64encode and urlsafe_b64encode? They use the same core algorithm, but urlsafe_b64encode swaps + and / for - and _ respectively, so the output is safe to embed directly in URLs without additional escaping.

Can I use base64 to shrink the size of data I’m sending? No — base64 always increases size by roughly a third. If you need to reduce size, look into actual compression (like gzip or zlib) instead, potentially combined with base64 afterward if the compressed binary output still needs to travel through a text-only channel.

Summary

Base64 is a foundational encoding technique for safely representing binary data as printable text, built on the simple mechanism of regrouping 3 bytes (24 bits) into four 6-bit characters. Python’s built-in base64 module makes encoding and decoding straightforward, as long as you’re careful about the bytes vs str boundary and choose the right variant (standard vs URL-safe) for your context. It’s everywhere in modern software — from HTTP auth headers to JWTs to embedded images — and understanding exactly what it does (and doesn’t) provide is essential to using it correctly and securely.

References

Exit mobile version