EncryptCodecencryptcodec
Blog/Security
SecurityMarch 17, 2026 · 7 min read

Base64 Is Not Encryption — Common Encoding Mistakes That Get Apps Hacked

A few years ago, a developer shipped a mobile app that stored user credentials in SharedPreferences as a Base64 string. They thought it was "obfuscated enough." It wasn't. A security researcher extracted the APK, ran atob() on the string in a browser console, and had plaintext credentials in under 30 seconds. The app was pulled from the store two weeks after launch.

Base64 is a reversible encoding scheme, not encryption. Anyone with a decoder — which is every developer and most script kiddies — can get your original data back instantly, no key required. If you're using it to protect anything sensitive, stop now.

What Base64 Actually Does

Base64 converts binary data into a text-safe representation using a 64-character alphabet (A–Z, a–z, 0–9, +, /). That's it. The entire point is to make arbitrary bytes safe for transport in systems that only handle text — email bodies, JSON fields, URLs, HTTP headers.

It was never designed to hide data. The spec doesn't mention confidentiality once. The output looks scrambled to a human glancing at it, which is exactly why developers mistake it for protection. SGVsbG8gV29ybGQ= decodes to Hello World — no secret, no key, no ceremony.

// Encoding
const encoded = btoa("Hello World");
console.log(encoded); // SGVsbG8gV29ybGQ=

// Decoding — takes less than a second
const decoded = atob("SGVsbG8gV29ybGQ=");
console.log(decoded); // Hello World

The Mistakes That Actually Show Up in Production

Storing passwords as Base64. This one appears in code reviews more often than it should. Passwords must be hashed with a slow, salted algorithm — bcrypt, Argon2, or scrypt. If your database is compromised and passwords are stored as Base64, an attacker has them all in plaintext within seconds.

Passing Base64-encoded secrets in URLs. Encoding a token or API key in Base64 before putting it in a query string doesn't protect it. URLs get logged — in nginx access logs, CloudFront logs, browser history, Referer headers, third-party analytics. A Base64 string in a URL is just a plaintext secret that's slightly harder to read at a glance.

Using Base64 as an obfuscation layer over encryption. Encrypting data and then Base64-encoding the output is fine — that's standard practice because ciphertext is binary and needs to be text-safe for transport. What's not fine is doing Base64 instead of encryption. The distinction matters: encode after you encrypt, not instead of encrypting.

Trusting Base64-encoded data from clients. JWTs, for example, are three Base64url-encoded segments separated by dots. The header and payload are completely readable by anyone. If you're storing sensitive data in a JWT payload assuming it's "encrypted," you're exposing it to every client that receives the token. The signature only proves the data hasn't been tampered with — it doesn't hide the contents.

Base64url vs Standard Base64 — The Gotcha That Breaks URLs in Prod

Standard Base64 uses + and / as characters. Both have special meaning in URLs. If you Base64-encode a value and drop it directly into a query string without URL-encoding it, + becomes a space and / can break path parsing. This is a real bug that shows up in password reset links and OAuth redirect flows.

Base64url solves this by replacing + with - and / with _, and omitting the = padding. Most JWT libraries use Base64url automatically, but if you're rolling your own token or encoding a binary blob for a URL, you need to use the URL-safe variant explicitly.

// Standard Base64 — NOT safe for URLs
const standard = btoa(String.fromCharCode(...new Uint8Array([251, 255, 254])));
console.log(standard); // +//+ — contains URL-unsafe characters

// Base64url — safe for URLs
function toBase64url(str) {
return btoa(str).replace(/+/g, '-').replace(///g, '_').replace(/=/g, '');
}
console.log(toBase64url("Hello World")); // SGVsbG8gV29ybGQ

When Base64 Is the Right Tool

Base64 is correct and appropriate for:

  • Embedding binary data in JSON (images, file attachments, cryptographic keys)
  • Encoding ciphertext or hashes for storage in text-based systems after encryption has already happened
  • HTTP Basic Auth header formatting (but the transport must be HTTPS — the encoding itself offers zero protection)
  • Encoding binary content for email (MIME)

The pattern is consistent: Base64 handles format and compatibility, never security.

The Fix Is Simple

If you need to protect data at rest, use AES-256-GCM. If you need to protect passwords, use Argon2id or bcrypt. If you need to verify data integrity and authenticity, use HMAC-SHA256. After you've encrypted or hashed, encode the binary output as Base64 to make it text-safe — that's the correct order of operations.

choose the right tool for the job
Base64 Encoding
Format conversion only — binary → text
plaintext
"Hello World"
no key
encoded
SGVsbG8gV29ybGQ=
reversibleyes
requires keyno
hides contentno
security protectionnone
processing speed100%
❌ Not protection — anyone can decode instantly
atob('SGVsbG8gV29ybGQ=') → 'Hello World' — no key, no ceremony, 30 seconds.

The one concrete thing you should do right now: search your codebase for btoa, base64_encode, Base64.getEncoder, and similar calls. For each one, ask: is this handling data that needs to be secret? If yes, you have a bug. Replace the encoding with actual encryption before it becomes a breach.

Share this post

Encode and decode Base64 safely

Free, browser-based — no signup required.

Frequently Asked Questions

Related posts