← Home

107. Base64 Module

Encoding and decoding binary data with base64

107. Base64 Module

Rohan’s manager wants RAM reports emailed automatically. Email is text-only — you can’t embed binary data directly. Base64 is the bridge.

He tries to attach a JSON report payload to a text-based webhook:

import base64, json

def encode_report(snapshot: dict) -> str:
    """Encode a snapshot dict as a base64 string for safe text transmission."""
    json_bytes = json.dumps(snapshot).encode()         # dict → JSON string → bytes
    encoded = base64.b64encode(json_bytes)             # bytes → base64 bytes
    return encoded.decode()                            # base64 bytes → str

def decode_report(encoded: str) -> dict:
    """Decode a base64 string back to a snapshot dict."""
    json_bytes = base64.b64decode(encoded)             # str → bytes
    return json.loads(json_bytes.decode())             # bytes → JSON string → dict

The pattern is always the same dance:

  • Encoding: str → bytes (.encode()) → base64 bytes (b64encode) → str (.decode())
  • Decoding: str → bytes (b64decode) → str (.decode()) → original

The result is a clean ASCII string like "eyJwZXJjZW50IjogODAuMX0=" — safe in JSON, URLs, emails, and anywhere text is expected.

Base64 is NOT encryption. Anyone can decode it. Use it for data transport, not security. If "YWRtaW46cGFzc3dvcmQ=" looks like a secure token to you, decode it — it’s "admin:password". Real security uses HTTPS + authentication, not base64 obfuscation.

💡 Fun fact: Base64 was invented in 1987 as part of the MIME email standard (RFC 989). It works by taking 3 bytes (24 bits) at a time and representing them as 4 ASCII characters from a 64-character alphabet (A-Z, a-z, 0-9, +, /). This is why base64 output is always about 33% larger than the input — 3 bytes become 4 characters. The trailing = characters are padding to make the output a multiple of 4.

⚠️ Watch out: base64.b64encode() returns bytes, not str. The result looks like b'SGVsbG8='. You must call .decode() on the result to get a plain str. This is the most common mistake — forgetting that there are two decode steps: b64decode() gives you back the original bytes, and then .decode("utf-8") converts those bytes to a string.

🤔 Think about it: Base64 increases data size by ~33%. If you’re transmitting 1 MB of binary data over an API that accepts only text, the actual payload becomes ~1.33 MB. For large files, is base64 the right approach? What alternatives exist for sending binary data over HTTP?

Learning objectives

  • Encode strings to bytes with .encode() before passing to base64 functions
  • Convert base64 bytes result to string with .decode()
  • Implement encode/decode roundtrip for string data
  • Understand that base64 is encoding, not encryption

Key concepts

  • base64.b64encode() — encodes bytes to base64 bytes
  • base64.b64decode() — decodes base64 bytes to original bytes
  • .encode() — str to bytes conversion
  • .decode() — bytes to str conversion
  • Roundtrip pattern — encode().decode() and b64decode().decode()

Try it

Concept detail

base64 — Encoding Binary Data as Text

Base64 encoding converts arbitrary bytes to a safe ASCII string representation. Used in: email attachments, data URLs, API tokens, JSON payloads with binary content.

The Full Dance

Both b64encode and b64decode work with bytes, not strings. You convert at both ends:

import base64

# Encoding:
message = "Hello, API!"
encoded = base64.b64encode(message.encode())  # str → bytes → encoded bytes
encoded_str = encoded.decode()                # encoded bytes → str
# Result: "SGVsbG8sIEFQSSE="

# Decoding:
decoded_bytes = base64.b64decode(encoded_str)  # str → bytes
original = decoded_bytes.decode("utf-8")        # bytes → str
# Result: "Hello, API!"

One-liner Pattern

# Encode:
b64 = base64.b64encode(s.encode()).decode()

# Decode:
original = base64.b64decode(b64).decode()

Common Mistakes and Fixes

MistakeErrorFix
b64encode("hello")TypeErrorb64encode("hello".encode())
return b64encode(...)Returns bytesreturn b64encode(...).decode()
b64decode(result)Returns bytes.decode("utf-8") after

Base64 is NOT Encryption

Anyone can decode base64 in seconds. It is an encoding format for safe text transport, not a security measure. Use HTTPS and proper authentication for security.

Solution

import base64

def encode_message(message: str) -> str:
    """Encode a string message to a base64 string."""
    encoded = base64.b64encode(message.encode())  # encode str to bytes first
    return encoded.decode()  # decode bytes result to str

def decode_message(encoded: str) -> str:
    """Decode a base64 string back to the original string."""
    decoded_bytes = base64.b64decode(encoded)
    return decoded_bytes.decode("utf-8")

def roundtrip(message: str) -> str:
    """Encode then decode a message — should return the original."""
    encoded = encode_message(message)
    return decode_message(encoded)

def encode_with_prefix(message: str, prefix: str = "MSG:") -> str:
    """Encode a prefixed message."""
    return encode_message(prefix + message)

Tests

HELLO_ENCODED = "SGVsbG8sIEFQSSE="  # base64 for "Hello, API!"
ARYAN_ENCODED = "QXJ5YW4="          # base64 for "Aryan"

def test_encode_message_returns_string():
    result = encode_message("Hello, API!")
    assert isinstance(result, str), f"encode_message should return str, got {type(result)}"

def test_encode_message_not_bytes():
    result = encode_message("Hello, API!")
    assert not isinstance(result, bytes), "encode_message must return str not bytes (call .decode() on result)"

def test_encode_message_correct_value():
    result = encode_message("Hello, API!")
    assert result == HELLO_ENCODED, f"Expected '{HELLO_ENCODED}', got '{result}'"

def test_encode_message_aryan():
    result = encode_message("Aryan")
    assert result == ARYAN_ENCODED, f"Expected '{ARYAN_ENCODED}', got '{result}'"

def test_decode_message_returns_string():
    result = decode_message(HELLO_ENCODED)
    assert isinstance(result, str), f"decode_message should return str, got {type(result)}"

def test_decode_message_correct_value():
    result = decode_message(HELLO_ENCODED)
    assert result == "Hello, API!", f"Expected 'Hello, API!', got '{result}'"

def test_roundtrip_restores_original():
    original = "Python is awesome"
    result = roundtrip(original)
    assert result == original, f"Roundtrip failed: got '{result}'"

def test_encode_with_prefix():
    result = encode_with_prefix("hello", "MSG:")
    decoded = decode_message(result)
    assert decoded == "MSG:hello", f"Expected 'MSG:hello' after decode, got '{decoded}'"

Resources