Let's get the conclusion out of the way first: Base64 is an encoding, not an encryption. There is no key. Anyone holding that string can turn it back into the original in a fraction of a second. Using it to "protect" a password or an API key accomplishes precisely nothing.
The problem it actually solves
Two kinds of thing are constantly at odds in computing. One only handles streams of raw bytes — images, archives, binary protocols. The other can only safely carry a subset of characters — early email, JSON, URLs, XML.
Base64 is the bridge between them. It re-expresses an arbitrary byte stream using an alphabet of 64 "safe" characters:
A–Z a–z 0–9 + /
Plus = as padding. Those 64 characters pass unharmed through almost every text protocol — nothing escapes them, truncates them or misreads them.
Why the output gets bigger
Every 3 bytes (24 bits) are sliced into 4 groups of 6 bits, and each group maps to one character. So 3 bytes in, 4 characters out — the size grows by a fixed factor of 4/3, about 133%.
That is why a 3 MB attachment becomes roughly 4 MB once encoded.
Three common variants
| Variant | Difference | Typical use |
|---|---|---|
| Standard | + / = | General purpose |
| URL-safe | +→-, /→_, padding dropped | URLs, filenames |
| MIME | A line break every 76 characters |
The reason the URL-safe variant exists is straightforward: + and / already mean something in a URL. A + in a query string is often read as a space, and / is the path separator.
The trap everyone falls into: multi-byte characters
The implementation people write first in a browser looks like this:
btoa('café') // throws InvalidCharacterError
btoa accepts only strings whose characters all sit within 0–255, while JavaScript strings are UTF-16 — anything outside Latin-1 simply will not fit. The correct route is to convert to bytes with TextEncoder first, then feed those bytes to btoa one at a time:
const bytes = new TextEncoder().encode(text);
let bin = '';
for (const b of bytes) bin += String.fromCharCode(b);
const out = btoa(bin);
Decoding runs the same road in reverse: turn the atob result back into a byte array, then decode it with TextDecoder. Skip that step and anything beyond ASCII turns into mojibake.
So when do you need encryption?
If what you want is "nobody else can read this", use actual cryptography: TLS for transport, AES-GCM for symmetric encryption, bcrypt or argon2 for stored passwords (which, strictly speaking, are hashes rather than encryption).
Base64 belongs where a binary payload has to travel through a text channel: embedding an image in a JSON field, inlining a small icon in a data: URI, wrapping a signature so it does not break the protocol around it. It is very good at those jobs. Just do not expect it to keep a secret.
Want to see the difference for yourself? Paste some accented text into the Base64 tool and toggle the URL-safe option to compare the output.