DailyTools
All articles
Developer ToolsJanuary 21, 20257 min read

Base64 Encoding: What It Is, How It Works, and When to Use It

Understand how Base64 encoding works under the hood, why it exists, its common use cases in web development, and why it is absolutely not a security mechanism.

DT
DailyTools Editorial · About

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.

text
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

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.

Try the free tool referenced in this article

Base64 Encoder / Decoder