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.
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.
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.
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.
Frequently Asked Questions
Related posts
Secure Password Reset Tokens — Expiry, Storage, and What Most Implementations Get Wrong
A practical guide to building secure password reset flows: token generation, expiry windows, one-time use enforcement, and the edge cases that cause real account takeovers.
Mar 30, 2026 · 7 min readIncident Response for Developers: What to Do When You Get Hacked
A practical incident response guide for developers covering detection, containment, eradication, recovery, and communication when a security breach happens.
Mar 29, 2026 · 9 min readPhishing Prevention: A Developer's Guide to SPF, DKIM, and DMARC
Understand how email spoofing enables phishing attacks and how to implement SPF, DKIM, and DMARC to protect your domain from being impersonated.
Mar 29, 2026 · 9 min read