Developer
Base64 Is Not Encryption: How It Works and When to Use It
Learn how Base64 turns bytes into text, with a worked example, why it adds a third in size, what Base64URL changes, and why it never protects secrets.
By Vigneshwaran M · 2026-10-03 · 8 min read
Base64 is a way of writing arbitrary bytes using only 64 safe, printable characters. It is one of the most common encodings in everyday development, and also one of the most commonly misunderstood. People see a string of random-looking letters and assume it must be protected. It is not. This article walks through how the encoding works, why it exists, where you will meet it, and what to reach for when you actually need secrecy. It is an educational overview, not security-audit advice. You can follow along with the Base64 encoder and decoder.
Why Base64 exists
Computers store everything as bytes, and a byte can hold any of 256 values. Many older text-based systems, such as email transports and some configuration or markup formats, were designed around readable characters. Raw bytes can contain control codes, line breaks or values that these systems would mangle, truncate or misread.
Base64 solves this by representing any sequence of bytes using a small alphabet that survives almost anywhere text does. The alphabet is the uppercase letters, the lowercase letters, the digits, plus + and /, with = used for padding. The standard definition is RFC 4648. The point is transport safety: the goal is to get bytes through a text-only channel intact, not to hide them.
How the encoding works, step by step
The whole trick is regrouping bits. Bytes normally arrive as blocks of eight. The encoder takes three of them side by side, giving a run of 24 bits, and then cuts that run into four pieces of six bits each. A six-bit piece can only hold a number from 0 to 63, which matches the alphabet size exactly.
Here is a small example you can check yourself. Take the three-letter word Cat. Its ASCII bytes, written in binary, are:
C = 01000011
a = 01100001
t = 01110100
Join them into one 24-bit stream and cut it into four 6-bit chunks:
010000 110110 000101 110100
Convert each chunk to a number:
010000 = 16
110110 = 54
000101 = 5
110100 = 52
Now look each number up in the alphabet. Positions 0 to 25 are A to Z, positions 26 to 51 are a to z, positions 52 to 61 are the digits 0 to 9, position 62 is + and position 63 is /. So 16 is Q, 54 is 2, 5 is F and 52 is 0:
Cat → Q2F0
Three bytes in, four characters out. I ran this in Node.js to confirm both the bit groups and the final string.
Padding with equals signs
Input rarely comes in neat multiples of three bytes. When the last group is short, the encoder pads the bits with zeros to complete the final 6-bit chunk and then appends = characters so the output length is a multiple of four.
Cat → Q2F0
Ma → TWE=
M → TQ==
Hello → SGVsbG8=
One leftover byte yields two characters and two =. Two leftover bytes yield three characters and one =. The padding carries no secret and no extra data. It simply tells the decoder how many bytes the final group really held.
Why the output is about one third bigger
Every three bytes of input become four characters of output, and each character occupies one byte in typical text encodings. The ratio is therefore 4 divided by 3, which is roughly 1.33. Put another way, you gain one extra byte for every three you encode, an increase of about 33 percent. Padding and any added line breaks push it slightly higher for short inputs. A 300-byte value becomes 400 characters. This is the price of fitting 8-bit data into a 6-bit alphabet, and it is why Base64 is a poor choice for large files when a binary channel is available.
Base64URL: the URL-safe variant
The standard alphabet includes + and /, and both have special roles in URLs and file paths. A / looks like a path separator, and a + can be read as a space in some query string conventions. The Base64URL variant, also defined in RFC 4648, keeps everything else identical but swaps those two characters: - replaces + and _ replaces /. Many systems also drop the trailing = padding, since the length of the data can be worked out without it.
To see the difference, take the three bytes FB FF BE. Their 6-bit groups are 62, 63, 62 and 62, which are the last two alphabet positions:
Standard: +/++
Base64URL: -_--
The bits are the same, only the spelling differs. Tokens that travel inside URLs or HTTP headers commonly use the URL-safe form. If you are curious how this looks in practice, see what a JWT is. For the related but separate topic of escaping characters in URLs, read what URL encoding is.
Where you will actually see Base64
- Data URLs. A small image or font can be written inline as a data URL in HTML or CSS, so the page needs no extra request. This is handy for tiny assets, though for larger images a normal file is usually better because of the size overhead and lost caching.
- Email attachments. Email was built for text, so binary attachments are typically carried in a Base64-style transfer encoding inside the message.
- Tokens and credentials in headers. Some schemes wrap a value in Base64 so it fits safely in a header. The wrapping is for formatting, not protection.
- JSON and XML payloads. Text formats cannot hold raw bytes directly, so a small binary blob is often embedded as a Base64 string. If you work with JSON a lot, the JSON formatter and our guide to common JSON errors are useful companions.
- Images in other tools. If you need to turn an image into another format before embedding it, try the image converter.
It is not encryption, and it is not hashing
Encryption needs a key. Without the key, the data should be unreadable. Hashing is a one-way fingerprint that cannot be turned back into the original. Base64 is neither. It has no key, no secret and no one-way property. It is a fixed, public recipe, so anyone who sees the output can reverse it immediately.
Here is a made-up example. Suppose someone stores this fabricated value, thinking it is hidden:
c2VjcmV0LXBhc3N3b3JkLTEyMw==
Decoding needs nothing but the string itself. No password is asked for, and no setup is needed. Running it through any decoder, including a single line of Node.js, returns:
secret-password-123
I verified this round trip by encoding the plain text and decoding it back. Because the process is reversible by design, relying on it as a lock is like sealing a letter in an envelope that has no flap. The look of the output is misleading: seeing a jumble of letters and digits feels like protection, but it is only a different spelling.
What to use when you need confidentiality
The right tool depends on the goal, and each of these is a topic in its own right.
- Protecting data in transit: use TLS, the protocol behind HTTPS, so traffic between a client and server is encrypted by well-tested software.
- Protecting data at rest or in messages: use encryption from a reputable, maintained library or platform feature, with keys managed properly. Do not invent your own scheme.
- Storing passwords: do not encrypt them and do not Base64 them. Use a dedicated password hashing function, designed to be slow and salted, provided by a trusted library.
Notice that Base64 can still appear alongside these tools. Encrypted bytes and hash outputs are binary, so they are often Base64-encoded afterward to be stored or sent as text. In that case Base64 is only the packaging, and the security comes from the cipher or hash inside.
Common mistakes
- Treating a Base64 string as a secret and pasting it into logs, tickets or public repositories.
- Assuming a token is safe because it looks scrambled. Many tokens are just Base64URL-encoded JSON that anyone can read, which the JWT decoder demonstrates with sample data.
- Mixing the standard and URL-safe alphabets, which causes decoding failures when
+and/meet-and_. - Forgetting padding rules, then wondering why a strict decoder rejects the input.
- Encoding text with characters outside the Latin range using a browser function that only accepts code points up to 255. Convert the text to UTF-8 bytes first, then encode those bytes.
- Inlining large images as Base64 and bloating the page by a third.
Quick checklist
- Do I just need to move bytes through a text channel? Base64 fits.
- Do I need secrecy? Use encryption or TLS, not Base64.
- Is this a password? Use a proper password hash.
- Is the value going into a URL or filename? Use the URL-safe variant.
- Is the payload large? Prefer a binary transfer to avoid the extra third.
- Is the text non-ASCII? Convert to UTF-8 bytes before encoding.
Try it yourself
Experiment with your own sample text in the Base64 encoder and decoder, and compare it with percent-escaping in the URL encoder and decoder. To see Base64URL in a real structure, open the JWT decoder, and read the background in what is a JWT and what is URL encoding. Use only made-up values when you experiment, never real credentials.