How Base64 encoding actually works
Base64 turns arbitrary binary data into plain ASCII text so it can travel through channels that only expect text. It shows up in data URIs, email attachments, JWTs, API keys, and configuration files — and it's routinely misunderstood as a form of encryption. Here's what it really does, one byte at a time.
The problem Base64 solves
Plenty of systems were designed to carry text, not raw bytes. Email bodies, JSON string values, URLs, XML documents, and HTTP headers all have characters that mean something structural — a quote, a newline, a null byte — and raw binary data will sooner or later contain those bytes and break the format.
Base64 sidesteps this by re-expressing any byte sequence using only 64 characters that are safe nearly everywhere: A–Z, a–z, 0–9, +, and /. Every one of them survives a trip through a text-only pipeline unchanged.
Three bytes become four characters
The trick is a change of units. A byte is 8 bits; a Base64 character encodes 6 bits. The least common multiple of 8 and 6 is 24 — so Base64 works on three bytes (24 bits) at a time and emits four characters.
Take the string Man. Its three bytes are 77, 97, and 110:
M= 77 =01001101a= 97 =01100001n= 110 =01101110
Concatenate those into one 24-bit run, then re-split it into four 6-bit groups:
01001101 01100001 01101110 → 010011 010110 000101 101110
Read as numbers, those groups are 19, 22, 5, 46. Now index into the Base64 alphabet, where positions 0–25 are A–Z, 26–51 are a–z, 52–61 are 0–9, 62 is +, and 63 is /. That gives T, W, F, u — so Man encodes to TWFu. No compression, no secret, just a different way of slicing the same bits.
What the = padding means
Not every input is a clean multiple of three bytes, so the last group may be short. Base64 zero-fills the final 6-bit group and then appends = characters to record how much of the last block was real data:
- 3 bytes in → 4 characters, no padding:
Man→TWFu - 2 bytes in → 3 characters + one
=:Ma→TWE= - 1 byte in → 2 characters + two
=:M→TQ==
Because the padding only ever appears at the end, a decoder can use it to know whether to discard one or two trailing bytes. Some implementations accept unpadded input and infer the length from the character count instead, which is why you'll see Base64 in the wild both with and without the trailing =.
Base64 costs you 33% in size
Four output characters for every three input bytes means encoded data is always about 133% of the original — a third larger. If the output is then stored as UTF-8 text (one byte per Base64 character, since they're all ASCII), that overhead is real bytes on disk and on the wire.
This is worth remembering before base64-ing a large image into a data URI or a JSON payload. It's cheap for a 2 KB icon and expensive for a 5 MB photo, and it's why HTTP generally moves binary bodies as binary rather than encoding them.
base64url: the URL-safe variant
The standard alphabet's last two characters are a problem in some contexts. + and / both have meaning inside URLs and filenames, and = is reserved in query strings. The base64url variant (defined alongside the standard in RFC 4648) fixes this with two substitutions and usually drops the padding:
+becomes-/becomes_- trailing
=is typically omitted
So bytes that encode to +/++ in standard Base64 become -_-- in base64url. This is the variant used for the three segments of a JSON Web Token, and generally anywhere the encoded value ends up in a path, query parameter, or filename. Decoding is the same process in reverse — you just translate the two characters back first.
Base64 is not encryption
This is the single most consequential misunderstanding about Base64, and it's worth stating bluntly: Base64 provides no confidentiality whatsoever. There is no key. The transformation is fully specified, public, and reversible by anyone in one step — including with the tool on this site.
Practical consequences:
- An HTTP Basic
Authorizationheader is justbase64(user:password). It's readable by anyone who sees the request, which is exactly why Basic auth requires HTTPS. - A base64-encoded API key in a config file, a JavaScript bundle, or a mobile app is not protected. It's obfuscated at best, and trivially recovered.
- The payload of a JWT is base64url, not encrypted — anyone holding the token can read every claim in it. The signature proves the token wasn't altered; it doesn't hide the contents.
If you need secrecy, you need actual encryption. If you need to prove a message wasn't tampered with, you want an HMAC or a signature. Base64 is a transport format, nothing more.
Text, bytes, and UTF-8
Strictly speaking Base64 encodes bytes, not characters — so when you encode a string, something has to turn that string into bytes first. Virtually always that's UTF-8. It matters because non-ASCII characters become multiple bytes before encoding: héllo 🚀 encodes to aMOpbGxvIPCfmoA=, which is longer than the seven visible characters suggest, since é takes two bytes and the emoji takes four.
Mismatched assumptions here are a classic source of mojibake — encode as UTF-8 and decode as Latin-1 and you'll get plausible-looking garbage rather than an obvious error.
Where you'll actually meet it
- Data URIs —
<img src="data:image/png;base64,…">inlines a small image with no extra request. - Email (MIME) — attachments are Base64'd, historically with line breaks every 76 characters, because SMTP was a 7-bit text protocol.
- JWTs — header, payload, and signature are each base64url, joined by dots.
- HTTP Basic auth — credentials encoded in the
Authorizationheader. - PEM keys and certificates — the block between
-----BEGIN-----and-----END-----is Base64-encoded DER. - Binary in JSON — JSON has no byte type, so binary fields are conventionally Base64 strings.
Common pitfalls
- Treating it as security. Covered above, and still the most common mistake in production code.
- Wrong variant. Feeding base64url into a strict standard decoder fails on
-and_; feeding standard Base64 into a URL breaks on+and/. - Stray whitespace. MIME-style line breaks are legal in some decoders and rejected by others. Strip them if you hit "invalid character".
- Missing padding. Strict decoders want the input length to be a multiple of four; add back the
=characters if a decode fails on a value pulled from a URL. - Double encoding. Base64 of Base64 is valid and silently doubles your overhead — worth checking when a value looks suspiciously long.
Try it yourself
The quickest way to build intuition is to encode a few things and watch the output. Encode M, then Ma, then Man, and see the padding disappear step by step. The Base64 encoder and decoder on this site runs entirely in your browser — including a hex view for binary results and a switch between the standard and URL-safe alphabets — so you can safely paste real tokens without them leaving your machine.