Base64 isn't encryption. I mention this because a surprisingly large number of developers treat it like it is. You'll find it in data URIs, JWT tokens, HTTP Basic Auth headers, and email attachments — and in every one of those places, it's doing exactly one thing: making binary data safe to transmit as text. That's it. Anyone with atob() and 30 seconds can reverse it. Understanding what Base64 actually does — and doesn't do — will save you from some genuinely embarrassing security mistakes.
The actual problem Base64 solves
Binary data — images, files, encryption keys — contains bytes across the full 0-255 range. Many transmission protocols were designed for printable ASCII text, not arbitrary bytes. SMTP, HTTP headers, HTML attributes, and XML all have restrictions: certain byte values cause premature string termination, others interfere with control characters, and non-ASCII bytes aren't supported at all in text-only contexts.
Base64 fixes this by mapping any binary data to a fixed set of 64 printable ASCII characters. Whatever binary you encode, the output contains only letters, digits, plus signs, and forward slashes — characters that are safe in virtually any text-based protocol.
How Base64 Encoding Works
Base64 uses an alphabet of 64 characters: the 26 uppercase letters (A–Z), the 26 lowercase letters (a–z), the digits 0–9, plus (+), and forward slash (/). A 65th character, the equals sign (=), is used for padding.
The encoding process takes 3 bytes of binary input (24 bits) and converts them to 4 Base64 characters (6 bits each). This is why Base64 output is always approximately 33% larger than the binary input — every 3 bytes become 4 characters. When the input length is not a multiple of 3, padding characters (=) are added to make the output length a multiple of 4.
Input: "Man" (ASCII: 77, 97, 110)
Binary: 01001101 01100001 01101110
Groups: 010011 010110 000101 101110
Base64: T W F u
Output: "TWFu"
Input: "Ma" (2 bytes — needs 1 padding char)
Output: "TWE="
Input: "M" (1 byte — needs 2 padding chars)
Output: "TQ=="Standard Base64 vs Base64URL
Standard Base64 uses + and / as its 62nd and 63rd characters. These are problematic in URLs: + means space in query strings, and / is a path separator. Base64URL (defined in RFC 4648) replaces + with - and / with _ to produce URL-safe output without percent-encoding.
JWT tokens use Base64URL encoding, which is why the three dot-separated sections of a JWT look slightly different from standard Base64 strings. When decoding JWT payload or header manually in JavaScript, you need to substitute - → + and _ → / before passing to atob().
Where you'll actually encounter Base64
- Data URIs: Embedding small images directly in HTML or CSS without a separate HTTP request. Example: <img src="data:image/png;base64,iVBORw0KGgo...">
- HTTP Basic Authentication: Credentials are Base64-encoded in the Authorization header — Authorization: Basic dXNlcjpwYXNzd29yZA==. Note: this provides zero security without HTTPS.
- JWT tokens: The header and payload sections of JSON Web Tokens are Base64URL-encoded JSON strings
- Email attachments: MIME email uses Base64 to encode binary file attachments for transmission through text-only SMTP servers
- API binary fields: Some APIs return binary data (public keys, fingerprints, cryptographic signatures) as Base64 strings for safe transmission in JSON
Base64 is not encryption — stop treating it like it is
Hot take: the Base64 padding character (=) trips up more production bugs than almost any other encoding quirk. Always check whether the receiving system expects it stripped. JWT libraries handle this automatically, but hand-rolled decoders often don't.
Base64 output looks like random characters, giving it a misleading appearance of secrecy. It's an illusion. Every Base64 library in every language can reverse it instantly. If you Base64-encode a password, an API key, or a secret value and transmit it, you've transmitted it in plaintext.
Sensitive values need proper cryptographic protection — AES-256-GCM or ChaCha20-Poly1305 for symmetric encryption, RSA or ECDSA for asymmetric operations. Base64 is appropriate only for making binary data safe to transmit in text-based protocols. It says nothing about confidentiality.
Base64 in JavaScript
// Encoding — btoa() works only with ASCII/Latin-1 strings
const encoded = btoa('Hello, World!'); // "SGVsbG8sIFdvcmxkIQ=="
// Decoding
const decoded = atob('SGVsbG8sIFdvcmxkIQ=='); // "Hello, World!"
// For Unicode/UTF-8 strings, use TextEncoder/TextDecoder:
function encodeUnicode(str) {
const bytes = new TextEncoder().encode(str);
return btoa(String.fromCharCode(...bytes));
}
function decodeUnicode(b64) {
const binary = atob(b64);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
// In Node.js (no btoa/atob pre-v16):
const encoded = Buffer.from('Hello').toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');The 33% overhead isn't free
Every 3 bytes of binary becomes 4 characters of text — that's the 33% size penalty. For a few hundred bytes it's negligible. For a 50KB PNG embedded in your CSS, you're serving a 67KB string. Worse, Base64-embedded images can't be cached separately by the browser, so that data gets re-downloaded on every page load.
Use Base64 data URIs only for very small resources: inline SVG icons under 2KB, CSS cursor images, or tiny placeholder images. For anything larger, an external URL with proper HTTP caching is almost always the better call.