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 excAlso 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 dataThe 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=Truefor 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.







