caduh

Base64 Encoding is Not Encryption — myth-busting what Base64 is actually for

4 min read

Base64 turns bytes into ASCII text for transport and storage. It is not a security mechanism. Learn how it works, common uses (MIME, URLs, JWTs), and what to use instead when you need secrecy or integrity.

TL;DR

  • Base64 is a binary‑to‑text encoding, not a security control. It’s fully reversible and uses no secret key.
  • It’s for compatibility (sending bytes through text‑only systems like email, JSON, URLs), not confidentiality or integrity.
  • For secrecy use authenticated encryption (AES‑GCM/ChaCha20‑Poly1305). For passwords use bcrypt/argon2. For integrity use HMAC/SHA‑256.
  • Seeing gibberish like QmFzZTY0IGlzIG5vdCBlbmNyeXB0aW9u doesn’t mean it’s secure—it’s just Base64.

What Base64 actually does (in 30 seconds)

  • Takes every 3 bytes (24 bits) of input, splits into 4 groups of 6 bits, and maps each 6‑bit chunk to a printable character (A‑Z a‑z 0‑9 + /).
  • If the input length isn’t a multiple of 3, it adds padding = to complete the final 4‑char block.
  • Result: text that survives email/JSON forms and old protocols.
  • Base64URL variant swaps +-, /_, and often omits = padding for URL safety.

Overhead: output is about 33% larger than the original bytes.


Common places you’ll see Base64

  • Email/MIME attachments (Content-Transfer-Encoding: base64).
  • Data URLs: data:image/png;base64,<...> for small inline assets.
  • JWTs: header and payload are Base64URL‑encoded JSON (not encrypted). Signature adds integrity, not confidentiality.
  • HTTP Basic Auth: Authorization: Basic <base64("user:pass")> — pure encoding; must ride over TLS.

Encoding vs Hashing vs Encryption (don’t mix these up)

| Property | Base64 Encoding | Hashing (e.g., SHA‑256) | Encryption (e.g., AES‑GCM) | |---|---|---|---| | Purpose | Represent bytes as text | Fingerprint / one‑way digest | Keep data secret | | Reversible? | ✅ Yes (no key) | ❌ No (by design) | ✅ Yes (with key) | | Integrity? | ❌ None | ⚠️ Collision‑resistant but unauthenticated | ✅ Use AEAD (auth + privacy) | | Needs a key? | ❌ | ❌ | ✅ | | Typical use | JSON/email transport, small embeds | Password hashing (with slow KDFs), checksums | Storing/transporting sensitive data |

Passwords should be stored with bcrypt/argon2/scrypt (slow, salted), not Base64 and not plain SHA‑256.


Tiny code examples

JavaScript (browser)

// Correct Unicode-safe encoding/decoding
const text = "Hello, 🌍";
const bytes = new TextEncoder().encode(text);
const b64 = btoa(String.fromCharCode(...bytes));
// decode
const bin = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const roundTrip = new TextDecoder().decode(bin);

Node.js

const b64 = Buffer.from("secret").toString("base64");         // c2VjcmV0
const raw = Buffer.from(b64, "base64").toString("utf8");      // "secret"

Python

import base64
b64 = base64.b64encode(b"secret").decode("ascii")   # c2VjcmV0
raw = base64.b64decode(b64).decode("utf-8")         # "secret"

Base64URL (JWT‑style) in JS

function toBase64Url(bytes){
  return btoa(String.fromCharCode(...bytes)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
}

“Looks encrypted” myths (and the facts)

| Myth | Reality | |---|---| | “It’s unreadable, so it’s encrypted.” | Base64 is trivial to decode with built‑in tools. | | “JWTs are encrypted.” | Only the signed parts are protected for integrity; contents are just Base64URL. Use JWE if you need encryption. | | “Basic Auth hides credentials.” | It doesn’t; it merely encodes them. Always use HTTPS (TLS). | | “Storing configs in Base64 keeps secrets safe.” | Anyone with access can decode. Use a secrets manager and encryption at rest. |


When is Base64 appropriate?

  • Moving binary through text‑only systems (JSON, environment variables, .proto in text form).
  • Embedding small assets inline (icons, tiny images).
  • Representing non‑printable bytes in logs/debugging (careful with secrets).

When not to use it: protecting secrets, obfuscating source, “encrypting” tokens, or storing passwords.


What to use instead (quick map)

  • Secrecy + integrity: AES‑GCM or ChaCha20‑Poly1305 via a well‑reviewed library (libsodium, WebCrypto, crypto.subtle).
  • Passwords: bcrypt/argon2 with per‑user salts and reasonable cost factor.
  • Integrity only: HMAC‑SHA‑256 with a secret key; or digital signatures (Ed25519/ECDSA) when public‑key is needed.
  • At rest: platform KMS (AWS KMS, GCP KMS, Azure Key Vault) + envelope encryption.

Pitfalls & quick fixes

| Problem | Why it’s risky | Fix | |---|---|---| | Base64‑storing secrets in git | Anyone can decode | Use a secrets manager or SOPS‑encrypted files | | Confusing Base64 with hashing | False sense of security | Learn the difference; use KDFs for passwords | | Unicode broken with btoa/atob | Exceptions, corrupted text | Use TextEncoder/Decoder or Node Buffer | | Massive data URLs | Bloated HTML, cache misses | Keep inline assets small; prefer CDN files | | Forgetting Base64URL differences | Bad JWT parsing, 404s | Use - and _, and strip = padding |


Quick checklist

  • [ ] Treat Base64 as encoding only, never as security.
  • [ ] Use Base64URL (-/_, no =) for URLs/JWTs.
  • [ ] Keep inline data: assets small; mind the 33% bloat.
  • [ ] Never “hide” secrets with Base64; use encryption/secrets managers instead.
  • [ ] Handle Unicode correctly when converting strings ↔ bytes.

One‑minute adoption plan

  1. Audit where you use Base64 today; label each use as encoding or mistakenly security.
  2. Replace any “Base64 for secrecy” with AEAD encryption or a secrets manager.
  3. Fix string handling using TextEncoder/Decoder or platform buffers.
  4. Standardize on Base64URL for tokens in URLs/JWTs.
  5. Document these rules in your project’s security guidelines.