Base64 is one of those things every developer bumps into but few stop to actually understand. You see it in JWTs, email attachments, data URIs, config files, API payloads. Then someone assumes it's encryption, or wonders why their 100KB image turned into 133KB of gibberish. Let's clear that up.

What Base64 actually is

Base64 is an encoding, not encryption. It takes arbitrary binary data and represents it using only 64 printable ASCII characters: A-Z, a-z, 0-9, plus + and /, with = used for padding. That's it. Nothing is hidden or secured — anyone can decode it back to the original bytes in milliseconds.

The mechanics are simple. Base64 chews through your data in groups of 3 bytes (24 bits), then splits those 24 bits into four 6-bit chunks. Each 6-bit value (0–63) maps to one character in the alphabet. Since 3 bytes become 4 characters, the output is always about 33% larger than the input. That's the tax you pay.

Why the size increase matters

Every byte in binary can hold 256 possible values, but a Base64 character only carries 6 bits of information. So you're spending 8 bits of storage to represent 6 bits of data. This is the trade-off: you make binary data safe to travel through text-only channels, but you pay roughly 33% more bandwidth and storage. Keep that in mind before inlining a large image as a data URI.

When you'd actually use it

The whole point of Base64 is moving binary data through systems that only expect text. Some common cases:

  • Data URIs — embedding a small icon or font directly in HTML/CSS to avoid an extra HTTP request.
  • Email attachments — MIME uses Base64 because SMTP was designed for 7-bit ASCII.
  • JWTs — the header and payload are Base64URL-encoded JSON (more on that variant below).
  • Basic Auth headers — Authorization: Basic dXNlcjpwYXNz is just user:pass encoded.
  • Storing binary blobs in JSON or XML — since those formats don't handle raw bytes gracefully.
  • Config and secrets in environment variables — encoding a PEM key or certificate so it fits on one line.

Notice a theme: it's always about compatibility, never about security. If you ever catch yourself thinking "I'll Base64 this password so it's safe," stop. It offers exactly zero protection.

Common gotchas

Standard vs URL-safe

The standard alphabet uses + and /, which are both problematic in URLs and filenames. The URL-safe variant swaps them for - and _, and often drops the = padding. JWTs use this variant. Mixing them up means your decoder chokes or produces garbage. Always know which flavour you're dealing with.

Padding confusion

The = characters at the end aren't decorative — they signal how many bytes were in the final group. One = means the last group had 2 bytes; two == means 1 byte. Some systems strip padding, some require it. If decoding fails, a missing or extra = is a prime suspect.

Character encoding of the source text

Base64 operates on bytes, not characters. If you're encoding a string, the result depends entirely on how that string was turned into bytes. Encode "café" as UTF-8 and you get different output than Latin-1. When you decode, you must know the original charset to reconstruct the string correctly. This bites people constantly with non-ASCII text.

Line breaks

MIME-flavoured Base64 wraps lines at 76 characters with newlines. Many decoders handle this, but some strict ones don't. If your decode fails on data copied from an email or PEM file, strip the whitespace first.

It's not compression

Base64 makes data bigger, not smaller. If you want to shrink data, gzip it — then Base64-encode the result if you need text safety. Doing them in the right order matters.

A quick worked example

Let's encode the string Hi!. Those three characters are the ASCII bytes 72 105 33, which in binary is:

H        i        !
01001000 01101001 00100001

Regroup those 24 bits into four 6-bit chunks:

010010 000110 100100 100001
  18     6      36     33

Map each value to the Base64 alphabet (0=A, 1=B, ... 26=a, ... 52=0):

18 -> S
6  -> G
36 -> k
33 -> h

So Hi! becomes SGkh. Because our input was exactly 3 bytes, no padding is needed. Try encoding just Hi (2 bytes) and you'd get SGk= with one padding character.

Doing it in code

Most languages have this built in. In JavaScript (Node or modern browsers):

// Browser
const encoded = btoa('Hi!');        // "SGkh"
const decoded = atob(encoded);      // "Hi!"

// For UTF-8 safety in the browser:
const enc = btoa(unescape(encodeURIComponent('café')));
const dec = decodeURIComponent(escape(atob(enc)));

In Node.js the Buffer API is cleaner and handles encodings explicitly:

const encoded = Buffer.from('café', 'utf8').toString('base64');
const decoded = Buffer.from(encoded, 'base64').toString('utf8');

Python is similarly straightforward with the base64 module, and you get urlsafe_b64encode for the URL variant out of the box.

Debugging tip: verify by hand

When something looks off — a corrupted token, a mangled data URI, a payload that won't decode — don't guess. Paste the value into a quick tool and see what actually comes out. I keep a browser tab open with a Base64 Encoder / Decoder for exactly this. Being able to round-trip a value in two seconds saves a surprising amount of head-scratching, especially when you're trying to figure out whether the problem is the encoding, the padding, or the charset.

The short version

Base64 is a reversible, non-secret way to represent binary data as text. Reach for it when you need to squeeze bytes through a text-only pipe, expect a ~33% size penalty, and pay attention to which alphabet variant and character encoding you're using. Get those two details right and most Base64 headaches disappear. Treat it as security and you'll create real problems for yourself.

Photo by Jake Walker on Unsplash