Base64 Myths: Why It's Not Encryption and Never Was
Base64 has no key.
Almost everything people get wrong about it follows from missing that one sentence, so it is worth stating before anything else. There is no secret, no password, no parameter that changes the output. The same input always produces the same output, and anyone — including this page — can reverse it instantly.
With that established, here are the myths.
Myth 1: it protects data
This is the one that causes real damage. Encoding a password, an API key or a customer record in Base64 before storing or transmitting it adds exactly zero protection. It makes the value unreadable to a human glancing at a screen, which is not the same thing as unreadable to an attacker who has the value.
The tell is in the shape of the string. cGFzc3dvcmQxMjM= is recognisable as Base64 at a glance — the character set, the length being a multiple of four, the trailing =. Anyone who has seen it before will decode it before they finish reading the line it appeared on.
If the requirement is confidentiality, you need encryption: a key, an algorithm, and a plan for where the key lives. If the requirement is "make this binary survive a text channel", Base64 is exactly right. Those are different requirements and the confusion between them shows up in breach reports with depressing regularity.
Myth 2: it compresses
The opposite. Base64 takes three bytes and represents them as four characters, so the output is about 33% larger than the input, plus up to two characters of padding.
The arithmetic: three bytes are 24 bits, split into four 6-bit groups, each mapped to one of 64 printable characters. Four characters where three bytes used to be.
This matters when inlining images as data URLs. A 90 KB image becomes roughly 120 KB of markup, and unlike a separate file it cannot be cached independently of the document carrying it, nor loaded in parallel. Inlining a small icon to save a request is reasonable. Inlining a photograph usually costs more than it saves.
Myth 3: it is a single standard
There are two alphabets in common use, and confusing them produces corrupted output rather than a clean error.
Standard Base64 (RFC 4648 §4) uses + and / for the last two positions. URL-safe Base64 (§5) uses - and _ instead, because + reads as a space in a query string and / is a path separator.
JWTs use the URL-safe variant with the padding stripped. This is why pasting a JWT segment into a standard decoder can fail: the decoder wants the = padding back. Append = until the length is a multiple of four and it works — a detail worth knowing when you are inspecting a token by hand rather than in a decoder that already handles it.
Myth 4: btoa handles text
It handles Latin-1, which is not the same thing. The browser's built-in btoa throws an InvalidCharacterError on any character above U+00FF:
btoa('café'); // InvalidCharacterError
btoa('日本'); // InvalidCharacterError
btoa('👋'); // InvalidCharacterError
The reason is that Base64 encodes bytes, and btoa has no idea which encoding your text should become. You have to decide, which in practice means UTF-8:
function encodeUtf8Base64(text) {
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary);
}
function decodeUtf8Base64(b64) {
const binary = atob(b64);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
Every "Base64 breaks on accents" bug is this missing step. The encoder went straight from characters to btoa without deciding what bytes those characters were.
What it is actually for
Base64 exists because a lot of infrastructure assumes text. Email bodies, JSON string values, HTTP headers, XML documents, YAML files — all of them handle printable ASCII reliably and mangle arbitrary bytes.
So the honest description is: Base64 is a way to move binary data through a channel that only guarantees text. Legitimate uses look like this:
- Attaching a file to an email.
- Embedding a small image in CSS or HTML as a data URL.
- Putting a binary signature inside a JSON field.
- Carrying credentials in an HTTP Basic Auth header — where, note, the encoding provides no security whatsoever, which is exactly why Basic Auth requires HTTPS.
That last example is the whole subject in miniature. Basic Auth encodes user:password in Base64 not to hide it, but because HTTP headers are text and a password might not be. The confidentiality comes entirely from the transport layer underneath.
The one-line test
If someone proposes Base64 as a security measure, ask what the key is.
There isn't one. That ends the discussion, and it is a faster explanation than any amount of theory about encoding versus encryption.