Base64 Is Not Encryption: What It Actually Hides
Disclaimer: This article is general technical guidance. Never paste production credentials, customer data or private keys into a tool you have not verified runs locally in your browser.
I once received an onboarding email that described a shared secret as "encrypted with Base64". It was not a joke, and it was not an isolated case. The same assumption shows up in code review comments, in tickets, and in the reasoning behind storing an API key as a Base64 blob inside a mobile app.
The string certainly looks like it is hiding something. c2VjcmV0 does not read like secret. But "does not read like" and "cannot be read" are very different claims, and confusing them is the whole problem.
The short version: Base64 is a transport encoding. It maps bytes onto 64 printable characters so binary data can travel through systems that only accept text — email, JSON, URLs, XML. There is no key, so anyone who has the string can reverse it instantly. If you need something to stay unreadable, you need encryption; if you need something to stay untampered, you need a signature. Base64 gives you neither.
What Encoding Actually Changes — and What It Cannot
Base64 takes three bytes at a time and rewrites them as four characters drawn from a fixed alphabet: the 26 uppercase letters, the 26 lowercase letters, the ten digits, and the two symbols + and /. That is 64 characters, which is exactly enough to express six bits per character. Three bytes (24 bits) therefore become four characters (4 × 6 bits).
The 64-character limit is the entire point. A byte can hold any of 256 values, and many of those values are control codes that some protocols treat as commands, refuse to carry, or silently mangle. The 64 Base64 characters are all printable and all safe in text contexts. That is the whole reason the scheme exists, and it is a size problem being solved, not a secrecy problem.
Two consequences follow directly, and they are worth holding onto:
- The output is longer, deterministically. Three bytes in, four characters out, so the encoded form is about 33% bigger.
- The transformation is reversible by anyone. No key is involved at any step, so there is nothing to be missing. The official definition is RFC 4648, and it fits in a few pages precisely because there is no secret material in it.
The Three Strings That Look Like Base64 but Are Not
Most confusion around Base64 comes from neighbouring schemes that produce similarly unfamiliar output. They solve different problems, and mixing them up leads to real bugs.
| Scheme | What problem it solves | Recognisable by |
|---|---|---|
| Base64 | Carrying arbitrary bytes through text-only systems | A–Z a–z 0–9 + /, often ending in == or = |
| Base64URL | The same, but safe inside a URL or a filename | - and _ instead of + and /, padding usually dropped |
| Percent-encoding | Carrying reserved characters inside a URL | Triplets such as %20 or %C3%A9 |
The two Base64 variants are the same algorithm with a different last two characters, which matters because + inside a query string is read as a space and / is a path separator. Padding deserves a note too: the = signs appear only at the end, and there can be one or two of them, purely so the decoder knows the final group was incomplete.
Decoding One by Hand Is Not as Hard as It Looks
You rarely need to do this, but walking through it once removes the mystique. Take c2VjcmV0:
- Split it into groups of four characters:
c2VjandcmV0. - Look up each character's six-bit value in the alphabet —
Ais 0,Bis 1, and so on through the digits and symbols. - Concatenate the six-bit values of a group to get 24 bits, then split those into three bytes.
- Read the three bytes as text. Here they come out as
s,e,c; the second group givesr,e,t.
The result is secret, and nothing about that reconstruction required a password. A Base64 encoder and decoder does the same four steps in microseconds, which is exactly why a Base64 string should never be treated as a place to hide anything.
Reading a JWT Without Any Tooling
JSON Web Tokens are where this misconception causes the most damage, because a JWT looks like a security artefact and its middle section is readable by design.
A JWT is three Base64URL segments joined by dots: header.payload.signature. Split on the dots, run the first two through a decoder, and you get plain JSON — the signing algorithm and key identifier in the header, the actual claims in the payload, such as a user id, a role and an expiry timestamp.
Read this part twice: the payload of a signed JWT is not secret. It is signed, not encrypted, and the point of signing is integrity — proving the token was issued by whoever holds the key — not confidentiality. Anyone who can see the token can read its claims. If those claims must stay private, the token has to be encrypted, which is a different structure defined separately in RFC 7519. Practical rule: put identifiers in a JWT payload if you like, but never put anything in it that you would mind appearing in a browser's developer console or a log file.
This is also why the signature matters more than the encoding. Base64URL carries the data; the signature is what stops someone editing the payload and re-submitting it. Strip the signature — or accept a token whose algorithm field you never validated — and the protection is gone regardless of how the characters look.
How to Tell Whether a String Is Base64
There is no magic prefix or checksum, so you are working with two weak signals. First, the character set: if the string contains anything outside A–Z a–z 0–9 + / =, it is not standard Base64 — though - and _ suggest the URL-safe variant. Second, the length: because every four characters decode to three bytes, a valid Base64 string has a length that is a multiple of four, counting the padding.
Neither signal is proof. Random hexadecimal text, for instance, uses a subset of the same alphabet and can happen to be divisible by four, and a short word such as test satisfies both tests while decoding to meaningless bytes. The only reliable check is to decode it and look at whether the result is sensible — printable text, or a file whose header bytes match a known format.
And when the decoded bytes are not text, do not assume something went wrong. An image, a compressed archive or a block of ciphertext decodes to bytes with no printable form, so a wall of replacement characters is the expected outcome, not an error. That is precisely the case Base64 was invented for: it is the wrapper that lets those bytes travel as text in the first place.
Frequently Asked Questions
Is Base64 a form of encryption?
No. Base64 is a transport encoding: it rewrites bytes using a 64-character alphabet so they survive systems that only handle text. There is no key and no secret, so anyone holding the string can turn it back into the original bytes. Encryption requires a key, and without that key the output is meant to be unreadable.
Can I put a password or API key in Base64 and call it protected?
No. Encoding an API key with Base64 adds no protection at all; anyone who intercepts the string can decode it in one step. Treat any Base64-encoded secret as plain text and protect it the way you would protect the original: keep it out of client-side code, out of logs, and out of version control.
Why does decoded Base64 sometimes look like garbage?
Because the original data was not text. Base64 encodes bytes, not characters, so an image, a compressed file or encrypted ciphertext decodes to bytes that have no printable representation. Seeing unreadable output is not a sign that decoding failed.
Does Base64 make a payload bigger?
Yes, by roughly a third. Every three bytes of input become four characters, so a 3 KB file becomes about 4 KB of text. That overhead is the price of representing arbitrary bytes using only printable characters.
Methodological note: This article was written and fact-checked by the Fengvi Editorial Team following a documented editorial methodology. All cited data comes from public sources.