Skip to content
🚀 10 tools live, free forever — the toolbox keeps growing
Home›Blog›Base64 Explained: How Encoding and Decoding Really Work
Guides

Base64 Explained: How Encoding and Decoding Really Work

How Base64 actually turns three bytes into four characters, why there are two alphabets and when each one matters, what the = padding is for, why "Base64 encoded" is never a security measure, and how to diagnose output that decodes to gibberish.

✍ By the Founder & CEO, Team BuildToolForge🗓 2026-10-17⏱ 11 min
base64encodingdecodingurl-safeutf-8
Security Tool Guide2026

Base64
Made Simple

How Base64 turns bytes into text

Standard & URL-safe · Padding · UTF-8

base64 · stdin
$ echo -n "Hi!" | base64
outSGkh
$ echo -n "SGkh" | base64 -d
outHi!
✓ 3 bytes become 4 characters · = pads the last group
Alphabet & padding
A-Z
0 to 25
a-z
26 to 51
0-9
52 to 61
+ or -
62
/ or _
63
=
Padding
Standard alphabetURL-safe variantUTF-8 handledNot encryption

What Base64 actually is

Base64 is a way of writing arbitrary bytes using only 64 safe characters. That is the whole idea. It is not encryption, not compression, and not a checksum. It solves one narrow problem: you have binary data, and the channel you need to push it through only accepts text.

That channel might be an email body, a JSON field, a URL, an HTTP header, a YAML config or a PEM file. Send raw bytes and something along the way will mangle them — a null byte truncates a string, a 0x0D gets rewritten by a line-ending conversion, a high byte gets reinterpreted in a different character set. Base64 sidesteps all of that by using only letters, digits and two punctuation marks, every one of which survives being treated as text.

The cost is size: the output is about a third larger than the input. That is the trade, and it is almost always worth it, because the alternative is data that arrives corrupted.

How three bytes become four characters

Base64 works on groups of three bytes. Three bytes are 24 bits, and 24 divides neatly into four groups of six. Each 6-bit group indexes into a 64-character alphabet, so three bytes in produce four characters out.

Take the three bytes of Man:

M = 77 = 01001101, a = 97 = 01100001, n = 110 = 01101110

Concatenate the bits and re-split them into sixes:

010011 010110 000101 101110 → 19, 22, 5, 46 → T, W, F, u

So Man encodes to TWFu. Four characters, no padding needed, because three bytes divided exactly. That “divided exactly” is the detail the next section is about.

The 64 characters

Indexes 0–25 are A–Z, 26–51 are a–z, 52–61 are 0–9, and 62 and 63 are the two that cause all the trouble. In standard Base64 they are + and /.

The alphabet, and why there are two

+ and / are perfectly fine in an email body and actively hostile in a URL. / is a path separator, and + means a space in a query string, so a Base64 value pasted into a URL can come back with its bytes changed and no error anywhere.

Hence base64url, a variant that keeps the first 62 characters and swaps the last two: - for + and _ for /. It is the encoding used by JSON Web Tokens — if you have ever read a JWT by hand, you have read base64url — and by anything else that travels in a URL or a filename.

The two variants are otherwise identical, which is exactly what makes mixing them dangerous. A string containing no +, /, - or _ at all is valid in both and decodes the same either way. One that does contain them decodes to different bytes depending on which variant the decoder assumes, usually with no error raised. Our Base64 encoder and decoder reports which alphabet it detected for exactly this reason.

Padding, and why decoders disagree about it

Input is rarely a multiple of three bytes. One leftover byte yields two characters, two leftover bytes yield three, and standard Base64 pads the group back up to four with = signs:

  • 3 bytes → 4 characters, no padding
  • 2 bytes → 3 characters + =
  • 1 byte → 2 characters + ==

The padding carries no data. It exists so a decoder reading a concatenated stream knows where one encoded value ends. If you are decoding a single standalone string, you do not need it at all — which is why base64url normally drops it, and why = is awkward in a URL anyway.

The practical problem

Some decoders require padding and reject an unpadded string outright. Others accept either. This is the single most common reason a token that works in one language fails in another with a bare “invalid base64”. The fix is almost always to add the padding back: append = until the length is a multiple of four. A decoder that is strict about padding is not broken, and neither is the encoder that omitted it — they are following different halves of the same specification.

Base64 is not encryption

This deserves its own section because the mistake is so common and so costly. Base64 has no key. It hides nothing. Reversing it is a single function call, available in every language and on every page like this one.

The canonical example is HTTP Basic authentication. An Authorization: Basic header is nothing more than username:password in Base64. It looks opaque, which is precisely the danger: it reads as “encrypted” to anyone who has not decoded one, while in fact anybody who can see the request can read the credentials. That is why Basic auth is only acceptable over HTTPS — the transport provides the secrecy, the encoding provides none.

The same applies to a JWT payload, to a Base64 blob in a config file, and to an API key someone “obfuscated” before committing it. If the only thing between an attacker and your secret is Base64, there is nothing between an attacker and your secret. Our guide to decoding and debugging JSON Web Tokens goes further into what that means for tokens specifically.

Where you actually meet Base64

Once you recognise the shape, you see it everywhere:

  • Email attachments. MIME has carried binary this way since the early nineties, wrapped at 76 characters per line.
  • data: URIs. A small image inlined into CSS or HTML as data:image/png;base64,…, trading an HTTP request for a third more bytes.
  • PEM files. Certificates and keys between -----BEGIN… markers are Base64 of the underlying DER, wrapped at 64 characters.
  • JWTs. Header and payload are base64url-encoded JSON; the signature is base64url of raw bytes.
  • Basic auth and some API credentials. As above, and with the same caveat.
  • Binary inside JSON. JSON has no byte type, so bytes travel as a Base64 string.

Why some Base64 has line breaks

MIME wraps at 76 characters and PEM at 64, because the formats they live in predate long lines. Those newlines are not part of the data and a decoder must ignore them. Most do; a few do not, which is why pasting a wrapped PEM body into a strict decoder sometimes fails until you strip the line breaks.

The size cost, and when it matters

Four characters per three bytes is a 4/3 expansion — about 33% — before padding and line breaks. For a one-off config value that is irrelevant. For anything on a hot path it is not.

A 10 KB image inlined as a data: URI costs about 13.3 KB of HTML, and unlike a real image it cannot be cached separately or lazily loaded. A token sent on every API call carries its overhead on every request. And compression does not rescue you: Base64 output compresses far worse than the original bytes, because encoding destroys the byte-level patterns a compressor looks for. Compress first, then encode — never the other way round.

Encoding text means encoding bytes

Base64 encodes bytes. It has no concept of characters, so “Base64-encode this string” is always shorthand for “turn this string into bytes, then encode those bytes”. The step in the middle is where things go wrong, because it needs a character encoding and the specification does not pick one for you.

In practice the answer is UTF-8, and modern APIs default to it. But if one side encodes text as UTF-16 or Latin-1 and the other decodes as UTF-8, the Base64 is perfectly valid and the text that comes out is mojibake. The giveaway is non-ASCII characters surviving badly while plain ASCII survives fine, since ASCII is a subset of UTF-8.

The round-trip test

If you are unsure whether a pipeline handles text correctly, encode a string with an accent and a non-Latin character, decode it again, and compare. Anything that mangles José or an emoji is losing information somewhere in the byte conversion, not in the Base64 itself.

When decoding goes wrong

Four diagnoses cover nearly every case:

  • “Invalid character”. Something that is not in the alphabet got in — most often a stray space, a quotation mark copied with the string, or a data:image/png;base64, prefix left on the front.
  • “Invalid length”. After stripping whitespace, the length modulo four is 1, which no valid Base64 can produce. A character was lost in transit.
  • It decodes, but to gibberish. Either the alphabets are mismatched (base64url decoded as standard, or the reverse), or the text encoding is mismatched as described above.
  • It decodes to bytes that are not text at all. This is not an error. Plenty of Base64 holds an image, a key or a serialised structure; a good decoder shows you a hex view rather than pretending the result is a string.

Working through those in order — characters, length, alphabet, encoding — resolves almost everything. And when a value simply will not decode anywhere, the most likely explanation is the dullest one: it was truncated. Base64 has no integrity check, so a string that lost its tail looks exactly like a string that never had one.

Related tools and guides:

Frequently Asked Questions

What is Base64 used for?⌄

Base64 carries binary data through channels that only accept text: email attachments, data: URIs in CSS and HTML, certificates and keys in PEM files, binary fields inside JSON, and HTTP headers such as Authorization: Basic. Anywhere raw bytes would be corrupted or rejected, Base64 gets them through unchanged.

Is Base64 encryption or encoding?⌄

Encoding. There is no key and no secrecy — decoding is a single function call that any browser can do. Base64 makes bytes safe to transport, not safe from being read. Never use it to protect a password, a token or an API key.

Why does my Base64 string end in = or ==?⌄

That is padding. Base64 works in groups of three bytes, and when the input does not divide evenly the last group is padded up to four characters with one or two = signs. The padding carries no data; base64url usually omits it, and some strict decoders insist on it, which is the usual cause of a string that decodes in one language but not another.

What is the difference between Base64 and base64url?⌄

Two characters and the padding. Standard Base64 uses + and / for the last two alphabet positions; base64url uses - and _ instead, because + and / have special meanings in a URL, and it normally drops the = padding. JWTs use base64url. Decoding one as the other silently produces different bytes.

How much larger does Base64 make my data?⌄

About 33%: every three bytes become four characters, plus padding and any line breaks. If size matters, compress before encoding rather than after — Base64 output compresses poorly because the encoding destroys the byte patterns a compressor relies on.

About the author
Founder & CEO, BuildToolForge
Follow on X: @Buildtoolforge

15+ years of hands-on experience in web development and security. Focused on practical web security: keeping websites secure, reliable and easy to troubleshoot. BuildToolForge exists to make these checks free, private and understandable. Tools run in your browser wherever possible, and nothing you enter into a tool is stored.

How we write our guides: Every guide is written from hands-on experience, checked against the official standards, and tested with our own tools on real domains. We update guides when standards or tools change. Found a mistake? Email contact@buildtoolforge.com.

Last updated: November 2, 2026