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 → dictThe 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
| Mistake | Error | Fix |
|---|---|---|
b64encode("hello") | TypeError | b64encode("hello".encode()) |
return b64encode(...) | Returns bytes | return 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}'"