Skip to content

Base64 is not encryption — what it is, when to use it and the URL-safe variant

How Base64 turns bytes into text, why it hides nothing, how to handle accents and emoji correctly and when you need the URL-safe alphabet.

By Felipe YamaokaPublished November 5, 20265 min read

The "encrypted" password in the config

Every now and then someone finds a configuration file with a line like password: c2VjcmV0MTIz and assumes it is protected. It is not. Paste it into any Base64 decoder and you get secret123 back, instantly, with no key.

Base64 is everywhere: in HTTP Basic authentication, in JWTs, in data URLs, in email attachments and in APIs that need to send a file inside JSON. It is useful precisely because it is simple and reversible. This guide explains what it does, what it does not do, and the two details that cause most bugs: text encoding and the URL-safe alphabet.

What Base64 does

Many channels were designed for text, not arbitrary bytes. JSON strings, HTTP headers, URLs and old email systems can break or alter binary data. Base64 solves this by representing any sequence of bytes with only 64 safe characters: A to Z, a to z, 0 to 9, + and /, plus = for padding.

The mechanics fit in one sentence: take the input 3 bytes at a time (24 bits), split them into 4 groups of 6 bits, and write each group as one of the 64 characters. When the input length is not a multiple of 3, the output is padded with one or two = so it stays a multiple of 4.

Two consequences follow:

  • The output is about 33% larger than the input, since every 3 bytes become 4 characters. 300 bytes of data become 400 characters.
  • It is completely reversible by anyone. There is no key, no secret and no randomness. The same input always produces the same output.

Encoding is not encryption

This is the point worth repeating. Base64 is an encoding, like writing a number in hexadecimal. It changes the representation, not the secrecy.

  • Encoding (Base64, hexadecimal, URL encoding) makes data fit a channel. Anyone can reverse it.
  • Encryption (AES, ChaCha20, RSA) makes data unreadable without a key.
  • Hashing (SHA-256, bcrypt, Argon2) turns data into a fingerprint that cannot be reversed, which is what you use for passwords.

HTTP Basic authentication is a good example of why the difference matters. The header Authorization: Basic dXNlcjpwYXNz is just user:pass in Base64. It is safe only because the connection uses HTTPS; Base64 adds no protection at all. The same goes for the payload of a JWT, which anyone can decode, as explained in the guide to reading a JWT.

If something must stay secret, encrypt it or keep it in a secret manager. If you need to store a password, hash it with a slow algorithm made for that. Never rely on Base64 to hide data.

Text, bytes and UTF-8

Base64 works on bytes, not on characters. Before encoding text, it has to be turned into bytes, and that step is where accents and emoji go wrong.

In browsers, the old functions btoa and atob only handle characters in the Latin-1 range. Look at what happens:

js
btoa("olá, mundo"); // "b2zhLCBtdW5kbw=="  Latin-1 bytes, not UTF-8
btoa("café ☕");     // throws InvalidCharacterError

The first result is valid Base64, but it uses one byte per accented letter instead of the UTF-8 encoding that almost every other system expects, so decoding it in another language produces garbage. The second simply fails. The correct way is to convert the text to UTF-8 bytes first:

js
function toBase64(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary);
}

function fromBase64(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

toBase64("café ☕"); // "Y2Fmw6kg4piV"

In Node.js and Bun, Buffer does this for you: Buffer.from("café ☕").toString("base64"). In Python, encode the string first: base64.b64encode("olá, mundo".encode()) gives b2zDoSwgbXVuZG8=.

The Base64 Encoder/Decoder on this site always uses UTF-8 in both directions, so accents and emoji round-trip correctly. When the decoded bytes are not valid UTF-8, for example because the Base64 holds an image or a binary file, it says so instead of showing broken characters.

The URL-safe variant

Standard Base64 uses +, / and =, and all three have special meanings in URLs and file names. + can turn into a space in a query string, / looks like a path separator and = separates keys from values.

The URL-safe alphabet, Base64URL, defined in RFC 4648, fixes this:

  • + becomes -
  • / becomes _
  • the = padding is usually dropped

For example, the text Hi?> is SGk/Pg== in standard Base64 and SGk_Pg in Base64URL. JWTs, many API tokens and signed URLs use the URL-safe form.

Two practical notes. First, not every library drops the padding: Python's base64.urlsafe_b64encode(b"Hi?>") returns SGk_Pg==, and some decoders refuse input without =, so you may have to add it back (until the length is a multiple of 4). Second, do not decode URL-safe input with a standard decoder, or - and _ will be rejected as invalid characters.

When to use Base64

Good uses:

  • Binary data inside text formats, such as a small file or a signature inside a JSON payload.
  • Data URLs for tiny images or fonts in CSS and HTML: data:image/png;base64,....
  • Standard protocols that require it, such as Basic authentication, email attachments (MIME) and JWT.
  • Opaque identifiers in URLs, using the URL-safe alphabet.

Bad uses:

  • Hiding secrets. Anyone can decode it.
  • Large files in APIs. The 33% overhead and the memory needed to encode and decode add up. Upload the file directly, or use multipart, and send a link.
  • Compression. Base64 always makes data bigger.

Common mistakes

  • Treating Base64 as protection for passwords, tokens or personal data.
  • Using btoa directly on text with accents or emoji.
  • Mixing the standard and URL-safe alphabets.
  • Forgetting that some decoders need the = padding.
  • Adding line breaks inside the encoded string, which some MIME tools do every 76 characters and many decoders reject.
  • Encoding the same data twice by accident, which is easy to spot when the result decodes to another Base64 string.

Summary

Base64 turns bytes into 64 safe characters so binary data can travel through text channels. It grows the data by about a third and anyone can reverse it, so it is never a security measure. Convert text to UTF-8 bytes before encoding, use the URL-safe alphabet for URLs and tokens, and reach for real encryption or hashing whenever something needs to stay secret.

Keep reading