Base64 is everywhere: email attachments, JSON payloads, data URIs, API tokens. Most developers know Base64 encoding "converts binary to text" and leave it there. Most practical problems with Base64 come from inconsistent implementations, not from how encoding works. Padding, URL safety, and the 33% bloat cause real bugs that look like something else.
It's Not Encryption
Production logs sometimes contain customer data encoded in Base64 under a field called encrypted. Base64 is an encoding scheme, not encryption. Anyone who sees a Base64 string can decode it in two seconds. There's no key and no secret, just a different representation of the same bytes.
Every language has a one-liner for Base64 decode:
// Browser or Node.js:
atob("SGVsbG8="); // "Hello"
// Python:
import base64
base64.b64decode("SGVsbG8=") # b"Hello"
That's it. No key, no algorithm negotiation, no security boundary crossed.
Base64 exists because channels like email, HTTP headers, and JSON don't handle raw binary well. You encode binary as text, send it through the text channel, and decode it on the other side. That's the entire purpose. If you need security, use actual encryption (AES, ChaCha20) on top of the encoded data.
The 33% Bloat: When It Matters
Base64 takes 3 bytes of input and turns them into 4 ASCII characters. That's a 33% overhead. For a 100-byte API response nobody notices. For large payloads it adds up fast.
Why 3 bytes to 4 characters? Base64 uses 64 characters, each representing 6 bits. Three bytes (24 bits) divide evenly into four 6-bit groups. That fixed 4:3 ratio is built into the design, not a quirk. The overhead is unavoidable: bytes are 8 bits and 8 doesn't divide evenly by 6, so the encoding rounds up to the next multiple.
| Input | Base64 size | Overhead |
|---|---|---|
| 1 KB icon | ~1.33 KB | 0.33 KB |
| 10 MB image | ~13.3 MB | 3.3 MB |
| 50 MB dump | ~66.5 MB | 16.5 MB |
Inline Base64 images in HTML or CSS make every visitor download that overhead. For small icons (under 2 KB) it's fine. For photos, serve the file directly and reference it with a URL. The extra HTTP request is faster than transferring 33% more bytes.
The memory cost is double what the table suggests. A server encoding a 10 MB file holds 13.3 MB in memory during Base64 encoding. The client needs another 13.3 MB to decode it. With a binary upload the data streams through. With Base64 the full payload lands in memory before anything reads it.
Padding Rules Depend on Your Language
Base64 groups input into 3-byte blocks. When the input isn't a multiple of 3, the last block gets 1 or 2 bytes of padding, represented as = characters.
Some encoders add padding. Some don't. Most modern decoders handle both cases, but not all. Python's base64 module strips padding by default. Java's Base64.getDecoder() accepts both. Go's encoding/base64 rejects missing padding outright:
data, err := base64.StdEncoding.DecodeString("SGVsbG8")
// illegal base64 data at input byte 7
A service that encodes without padding and sends the result to a Go consumer will fail at the decode step. No error message points to the missing = sign. It looks like corrupted data. A URL-safe variant where the = characters weren't URL-encoded caused the backend to parse them as query parameters.
RFC 4648 defines Base64 and its variants. The spec says padding is required, but implementations disagree. If you control both encoder and decoder, you can strip padding safely. If you're integrating with a third party, leave it on. The inconsistency between languages costs more debugging time than it should. It's invisible until a cross-service call breaks at 2 AM.
URL-Safe Base64
Standard Base64 uses + and / as the 62nd and 63rd characters. Neither is safe in URLs. URL-safe Base64 replaces them with - and _.
A signed URL works in Postman but fails in the browser. The culprit is often a + in the signature segment, decoded as a space by the browser's URL parser. Switching to URL-safe Base64 fixes it. The Base64 encoder/decoder supports both variants, including the option to strip or keep padding.
When to Use It (and When Not To)
Base64 works well for:
- Small binary attachments in JSON APIs (generated PDFs, thumbnails)
- Data URIs for tiny assets under 2 KB
- Storing binary data in legacy databases with text-only columns
Don't use it for:
- File uploads. Sending a 20 MB Base64 string instead of
multipart/form-dataloses streaming, adds 33% overhead, and increases memory on both sides. - Obfuscation. If you think Base64 hides anything, one
atob()call proves otherwise. - Large inline assets. Every visitor pays the bloat tax. Serve files directly.
Base64 solves a specific problem: binary over text channels. It's not a security measure, not a compression algorithm, and not a substitute for file uploads. The bugs come not from how it works, but from assuming it works the same way everywhere.