Base64 Is Not Encryption: A Practical Guide for Text and Small Files
Base64 often appears in places that make it look more mysterious than it is: API payloads, email attachments, Data URLs, configuration files, and token segments. It is easy to mistake a long Base64 string for protected
Base64 often appears in places that make it look more mysterious than it is: API payloads, email attachments, Data URLs, configuration files, and token segments.
It is easy to mistake a long Base64 string for protected data. It is not protected. Base64 is an encoding that represents bytes with printable text characters. Anyone who has the value can decode it.
That distinction matters when you are handling text, small files, or values that might contain secrets.
What Base64 actually does
Computers store data as bytes. Base64 groups three bytes, or 24 bits, then splits those bits into four groups of six. Each six-bit value becomes one character from a 64-character alphabet:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
For example, the UTF-8 bytes for cat become:
cat -> Y2F0
The string Y2F0 looks different from cat, but it contains no secret. Decode it and the original bytes return immediately.
RFC 4648 defines the standard Base64 alphabet, padding behavior, and the URL-safe variant used by many web formats. The RFC is worth reading when you need to implement or validate Base64 rather than simply call a library.
Why Base64 makes data larger
Base64 encodes every three input bytes as four output characters.
For input data with n bytes, standard Base64 has this output length:
4 Γ ceil(n / 3)
That is roughly a 33% increase before you account for JSON quotes, a data: URL prefix, or any transport overhead.
A 3 MiB image does not become easier to send because it is Base64 encoded. It becomes larger, and placing that large string inside JSON or HTML can add parsing and memory costs.
Base64 is useful when a system needs text-safe transport. It is not a compression format. If size is the problem, compress the original data or choose a more suitable file format.
The = characters are padding, not data
A Base64 string often ends in one or two = characters:
YQ== // "a"
YWI= // "ab"
YWJj // "abc"
The padding tells a decoder that the final input group contained one or two bytes instead of the usual three.
Some protocols require standard padded Base64. Others define their own rules. A decoder should not quietly guess which form a value uses, especially when it is parsing structured data. Check the specification for the format you are working with.
Base64URL is related, but not interchangeable
Standard Base64 uses + and /. Those characters can be awkward in URL paths, query strings, and filenames.
Base64URL replaces them with - and _:
Standard Base64: + and /
Base64URL: - and _
Base64URL commonly omits trailing = padding as well.
This is why the segments in a JWT often look like Base64 but do not contain +, /, or =. A JWT segment is usually Base64URL encoded JSON, not encrypted data. You can decode its header and payload, but you must not trust their contents until the signature has been verified.
Use Base64URL only when the destination format expects it. Do not replace characters by habit and assume every Base64 decoder will accept the result.
Text needs UTF-8 bytes first
JavaScript strings are not byte arrays. This catches people when they try to use btoa() directly with emoji, Chinese text, or other non-ASCII characters.
This may work:
btoa("hello");
This may throw or produce the wrong result:
btoa("δ½ ε₯½");
The safe sequence is:
- Convert the JavaScript string to UTF-8 bytes.
- Encode those bytes as Base64.
- Reverse the process when decoding.
function bytesToBase64(bytes) {
let binary = "";
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary);
}
function textToBase64(text) {
return bytesToBase64(new TextEncoder().encode(text));
}
function base64ToText(value) {
const binary = atob(value);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
TextEncoder converts JavaScript strings to UTF-8 bytes, while TextDecoder converts valid UTF-8 bytes back to text. MDN's Encoding API reference explains this byte-to-text boundary.
Using fatal: true when decoding is often the right choice for developer tools. It prevents arbitrary binary data from being silently presented as damaged text.
Binary files are not text
A PNG, PDF, ZIP archive, or audio file can be Base64 encoded because Base64 works on bytes. That does not mean the decoded result is UTF-8 text.
When you decode a Base64 file, the reliable workflow is:
- Decode the Base64 into bytes.
- Save those bytes as a file.
- Open the file with software that understands its format.
Trying to display arbitrary decoded bytes in a text area can corrupt the result or make it appear as unreadable characters.
I built ToolExo's Base64 Encoder and Decoder around that distinction. It encodes UTF-8 text separately from local files, validates standard Base64 and Base64URL inputs, and lets you download decoded bytes as a file. File processing stays in the browser and is limited to 10 MiB, which keeps the tool suited to small local tasks rather than large asset pipelines.
Data URLs are useful for small, deliberate cases
A Data URL embeds data directly in a URL-like string:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
They can be useful for tiny assets, test fixtures, or self-contained examples. They are usually a poor choice for normal application images.
Why?
- The Base64 payload increases the asset size.
- The browser cannot cache the embedded asset independently.
- A large inline string makes HTML, CSS, or JSON harder to inspect and maintain.
- Content Security Policy and browser behavior may limit where Data URLs are accepted.
MDN documents the data: URL format and its practical limitations.
For a normal website image, serve the file through a real URL. For a small one-off fixture or local prototype, a Data URL can be convenient.
Base64 does not protect secrets
This is the part worth repeating.
Do not Base64 encode a password, API key, private key, session token, database credential, or secret configuration value and call it protected. Encoding only changes representation.
If you see this in a log:
c3VwZXItc2VjcmV0LWtleQ==
assume it is exposed until proven otherwise. A decoder can recover the original value in seconds.
Encryption requires a cryptographic algorithm and a key. Hashing has a different purpose again: it is designed to make recovery impractical, not to preserve the original bytes for later decoding.
Base64 is appropriate when you need reversible binary-to-text conversion. It is the wrong tool when the goal is confidentiality.
A practical checklist
Before encoding a value, ask:
- Do I need text-safe transport, or do I need secrecy?
- Is the receiving system expecting standard Base64 or Base64URL?
- Am I encoding UTF-8 text or arbitrary binary bytes?
- Will the 33% size increase create a problem?
- Should this file use a normal URL instead of a Data URL?
- Am I accidentally putting a secret into logs, source code, or client-side storage?
Base64 is simple once its job is clear. It moves bytes through text-only spaces. It does not compress those bytes, validate their meaning, or keep them private.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.