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
Prompt Injection: Why You Can't Fix It With a Better Prompt
Prompt injection isn't a filtering problem you can prompt your way out of. It's an architecture problem — the model can't tell your instructions from the data it's reading. Here's what actually contains it.
Jul 28, 2026 · 11 min readPhishing URLs: How Attackers Fake Domains (and How to Read Them)
Typosquatting, homograph attacks, subdomain deception, and the @ trick — learn the handful of URL tricks behind most phishing, and the one habit that beats all of them.
Jun 13, 2026 · 7 min readWhat Is CSV Injection — and How to Stop It in Your Export Feature
CSV (formula) injection turns a harmless 'export to spreadsheet' button into code execution on whoever opens the file. Here's how the attack works and how to prevent it.
Jun 13, 2026 · 7 min read