What Base64 is and why we use it: how the encoding works
How Base64 turns 3 bytes into 4 characters, why the output is about 33% larger, what the trailing = means, URL-safe Base64, and handling non-ASCII text, based on RFC 4648.
Email attachments, images inlined in a page as data:image/png;base64,iVBOR…, the middle part of a login token (JWT): all of them are Base64. That long run of letters and digits isn’t a cipher. It is a way of writing binary data as text.
Why it exists
Email (SMTP), JSON, HTML and URLs were built to carry text. Binary data such as images or zip files contains bytes that can be read as line breaks, control characters or quotes. Put it in raw and it gets mangled in transit or breaks the syntax. Base64 rewrites any bytes using only 64 characters that are safe everywhere.
Three bytes become four characters
The rules are in RFC 4648.
- Take the input 3 bytes (24 bits) at a time.
- Split the 24 bits into four 6-bit pieces. Each piece is a value from 0 to 63.
- Map each value to a character.
| Value | Character |
|---|---|
| 0–25 | A–Z |
| 26–51 | a–z |
| 52–61 | 0–9 |
| 62 | + |
| 63 | / |
Here is “Man”:
M a n
01001101 01100001 01101110 ← 3 bytes
010011 010110 000101 101110 ← four 6-bit pieces
19 22 5 46
T W F u → "TWFu"
What the trailing = means
When the input doesn’t divide evenly into 3-byte groups, the last group is short. The missing bits are filled with zeros, and the character positions that weren’t produced are filled with =, so the output length is always a multiple of 4.
| Input | Output |
|---|---|
Man (3 bytes) |
TWFu |
Ma (2 bytes) |
TWE= |
M (1 byte) |
TQ== |
So there are never more than two = at the end, and the decoder uses them to know how many bytes the original had.
It is about 33% bigger
Three bytes become four characters, so the output is 4/3 the size of the input: n bytes encode to 4 × ⌈n ÷ 3⌉ characters. A 300 KB image inlined as Base64 is about 400 KB. Email (MIME, RFC 2045) also wraps lines at 76 characters, which adds line breaks on top.
Inlining a small icon as a data: URI to save a request is fine. Keep large images as separate files: they can be cached, and they transfer smaller.
Base64 is not encryption
This is the most common misunderstanding. Base64 has no key: anyone can reverse it with the same table. “Hiding” a password or personal data in Base64 protects nothing. The payload in the middle of a JWT is only Base64URL-encoded, so anyone can read it. What a JWT guarantees is that the content can’t be forged (its signature), not that it is secret.
URL-safe Base64 (Base64URL)
Standard Base64’s + and / mean something in URLs (/ separates paths, + is a space in form data). So RFC 4648 defines Base64URL, which changes just two characters.
| Value | Standard Base64 | Base64URL |
|---|---|---|
| 62 | + |
- |
| 63 | / |
_ |
Base64URL often drops the trailing = padding too; JWTs (RFC 7515) do. If decoding fails with an invalid-character error, first check which of the two you were given.
Non-ASCII text
Base64 works on bytes. To encode text you first turn it into bytes, usually as UTF-8. “é” is C3 A9 in UTF-8 and w6k= in Base64.
JavaScript’s btoa() treats each character as one byte, so any character above U+00FF (Korean, Japanese, emoji) throws InvalidCharacterError. Convert to UTF-8 bytes first:
const bytes = new TextEncoder().encode("日本");
const b64 = btoa(String.fromCharCode(...bytes)); // "5pel5pys"
When decoding, read the bytes back as UTF-8. Base64 made from text in another encoding (Shift_JIS, EUC-KR, Windows-1252) comes out garbled if you decode it as UTF-8.
Try it
The Base64 encoder and decoder handles accented letters, Korean, Japanese and emoji as UTF-8, and supports the URL-safe form. The conversion runs in your browser; your input is not sent anywhere. Type “Man”, “Ma” and “M” in turn to watch the padding rule at work.
References
- RFC 4648, The Base16, Base32, and Base64 Data Encodings
- RFC 2045, MIME Part One (6.8 Base64 Content-Transfer-Encoding)
- RFC 7515, JSON Web Signature (JWS)