Base64 Encoding Explained
Base64 shows up everywhere binary data has to travel through a channel that only understands text — email attachments, JSON payloads, JWTs, data URIs. It has a reputation for being cryptic, but the whole scheme is one small idea: take three bytes, re-slice their 24 bits into four groups of six, and print each group as one of 64 safe characters. This guide walks that mechanism byte by byte, explains the = padding and the URL-safe variant, is honest about the 33% size cost, and settles the single most common misconception — that Base64 is a kind of encryption. It is not.
The problem Base64 solves
Computers store everything — images, fonts, compiled code, encryption keys — as raw bytes, and a byte can be any of 256 values from 0 to 255. Many of those values are not printable characters: some are control codes, some are invalid in a given text encoding, and a few (like a null byte or a newline) actively break protocols that expect plain text. Email built on classic SMTP, for decades, guaranteed only 7-bit ASCII; a URL reserves characters like ?, & and / for its own structure; JSON and XML have no native "byte" type at all. Drop raw binary into any of these and it gets mangled or rejected.
Base64 is the workaround. It re-expresses any sequence of bytes using a small, deliberately boring alphabet of characters that survive intact through text pipes. The trade is simple and worth stating plainly: you give up some size (about a third more) in exchange for the guarantee that your bytes arrive unaltered. It is a transport format, not a storage or security format.
The 64-character alphabet
The name says it: Base64 encodes data in a numbering system with 64 symbols. Those 64 characters were chosen because they are printable and safe almost everywhere:
That is 26 + 26 + 10 + 2 = 64 data symbols, plus = which is not one of the 64 — it only ever appears at the end as padding. Six bits can count from 0 to 63, which is exactly 64 distinct values, so one 6-bit chunk maps to exactly one alphabet character with nothing wasted. That neat fit between "6 bits" and "64 symbols" is the reason the number 64, and not 32 or 128, sits at the heart of the scheme.
Three bytes become four characters
Here is the mechanical core. A byte is 8 bits; three bytes are 24 bits. The Base64 alphabet uses 6 bits per character; four characters carry 24 bits. Because 3 × 8 = 24 = 4 × 6, three bytes line up perfectly with four characters, with no bits left over. So the encoder always chews through the input in 3-byte groups and emits 4 characters for each — the bit boundaries simply get redrawn from eights to sixes.
Nothing is added or removed; the same 24 bits are read with a different ruler. Because a 6-bit chunk can straddle the boundary between two original bytes, you cannot look at a single Base64 character and name the byte it came from — the mapping only makes sense in complete groups of four.
Worked example: encoding "Man"
The classic demonstration, and the one from the original specification, is the three-letter word Man. Its ASCII byte values are M = 77, a = 97, n = 110. Write those as 8-bit binary and lay all 24 bits in a row, then re-slice into four 6-bit groups:
Follow one boundary to see the re-slicing in action: the first output character takes all 6 of its bits from the start of the M byte (010011 = 19 = T). The second character takes the last 2 bits of M plus the first 4 bits of a (01 + 0110 = 010110 = 22 = W). That crossing of the byte boundary is precisely why Base64 has to work in groups rather than character by character. Three bytes in, four characters out, no padding needed — because 3 is a whole group.
Why = padding appears
Real data is rarely a tidy multiple of three bytes, so the last group is often short. Base64 handles a 1- or 2-byte tail by zero-filling the missing bits to complete the six-bit chunks it can form, then writing = for each character position that had no real input byte behind it. The padding keeps every encoded string a multiple of 4 characters, and it records how many bytes the final group actually carried:
So one = means "the last group held two bytes" and == means "it held one." A decoder reads the padding to know exactly how many bytes to reconstruct, discarding the zero-fill bits. Some strict decoders require the padding to be present; others tolerate its absence — which brings us to the variant that usually drops it.
The URL-safe variant
Two of the 64 characters — + and / — are awkward in the very places Base64 is often used. In a URL, / is a path separator and + can be read as a space; in a filename, / is illegal on most systems. The URL-safe alphabet defined in RFC 4648 §5 fixes this by swapping just those two, and typically omitting the padding:
| Index | Standard (§4) | URL-safe (§5) |
|---|---|---|
| 0–61 | A–Z a–z 0–9 | A–Z a–z 0–9 (same) |
| 62 | + | - (hyphen) |
| 63 | / | _ (underscore) |
| pad | = | usually omitted |
Only positions 62 and 63 and the padding change; the first 62 symbols are identical, so most of a string looks the same in both forms. This is the encoding you see in JSON Web Tokens, where the header and payload are URL-safe Base64 joined by dots, and in many "copy this link" tokens. When decoding, a careful tool restores -→+ and _→/ and re-adds any missing = first, so it accepts either variant without you having to say which you have.
The ~33% size overhead
Because four output characters carry the information of three input bytes, encoded data is larger by a factor of 4 ÷ 3 ≈ 1.333 — roughly a third bigger, before you count padding or any line breaks that some formats insert every 76 characters. For a short token that is irrelevant. For a 3 MB image inlined into a page it means about 4 MB of text, which the browser then has to parse — so Base64-inlining large assets can cost more than the HTTP request it was meant to save. The rule of thumb: Base64 is cheap for small things and expensive for big ones. When a payload is large, sending raw bytes or compressing first is almost always the better call.
data: URIs — a worked use
One place the size trade often pays off is the data URI, which embeds a whole resource directly in HTML or CSS instead of linking to a separate file. The format is data:[media-type];base64,[the-data]. A tiny 1×1 transparent PNG, for example, becomes a single self-contained string:
Everything after base64, is the file's bytes, Base64-encoded. The browser decodes it back to the original PNG and renders it with no extra network request — handy for small icons, inline fonts and email images. The cost is the 33% inflation plus the fact that inlined data cannot be cached separately, so data URIs suit small, rarely-changing assets and not large photos.
Base64 is encoding, not encryption
This is the point to carry away above all others. Base64 hides nothing. There is no key, no password, no secret in the transformation — it is fully public and fully reversible, which is exactly what any decoder does in microseconds. The string QWxhZGRpbjpvcGVuIHNlc2FtZQ== looks scrambled, but it decodes straight back to Aladdin:open sesame for anyone who pastes it. Base64-ing a password, an API key or a config secret gives you precisely zero confidentiality; it only changes the alphabet the bytes are written in. If data must be protected, use real encryption — AES, TLS, age — and treat Base64 as what it is: a way to make bytes portable, never a way to make them private.
Paste text and watch it convert live, with full UTF-8 support, a URL-safe toggle and byte counts — nothing leaves your browser.
Putting it together
Base64 is one of those tools that feels mysterious until you see the bits move, and then it is almost boring: three bytes, 24 bits, four six-bit characters drawn from a 64-symbol alphabet, with = padding to square off a short final group and a URL-safe swap of two characters for the web. Its cost is a predictable 33% growth; its job is to let binary ride safely inside text; and its one hard limit is that it offers no security whatsoever. Keep those four facts straight — alphabet, grouping, padding, "not encryption" — and every Base64 string you meet, from a data URI to a JWT, becomes readable at a glance.
Open the Base64 Encoder / Decoder → to encode or decode any string and see the byte counts for yourself, or browse all Polyatic tools.