"I'll Base64-encode it so no one can read it." This line appears in code reviews, Slack messages, and Stack Overflow answers more often than it should. Base64 is not encryption. It is not obfuscation. It is not security of any kind. Decoding it takes one function call and zero knowledge of any key or secret. This article explains what Base64 actually is, why the confusion persists, and what to use instead when you genuinely need to protect data.
What Base64 Actually Is
Base64 is an encoding scheme — a reversible mapping between binary data and a set of 64 printable ASCII characters (A–Z, a–z, 0–9, +, /, with = as padding).
Every 3 bytes of input become 4 Base64 characters. The algorithm is completely deterministic and requires no key:
// Encoding
btoa("hello") // → "aGVsbG8="
btoa("secret password") // → "c2VjcmV0IHBhc3N3b3Jk"
// Decoding — one call, no key needed
atob("c2VjcmV0IHBhc3N3b3Jk") // → "secret password"That's it. Anyone with access to the encoded string can decode it instantly, in any language, in any environment, with no additional information.
Why the Confusion Exists
Base64-encoded strings look like encrypted data. They're full of mixed-case letters, numbers, and special characters, and they bear no visible resemblance to the original input. This visual noise is enough to fool a non-technical observer — and, surprisingly often, a technical one.
The pattern also gets reinforced by proximity. Base64 appears constantly in security contexts:
- HTTPS certificates are Base64-encoded (PEM format)
- JWT tokens use Base64url encoding for their header and payload
- HTTP Basic Authentication sends credentials as Base64
- SSH keys are stored as Base64
- Encrypted ciphertext is often Base64-encoded for transport
Seeing Base64 in all these security-adjacent places leads developers to conflate the encoding with the security. The encoding is just the wrapper; the security comes from the cryptographic operations underneath.
Encoding vs Encryption: The Core Difference
The distinction is simple and absolute:
| Encoding (Base64) | Encryption (AES, RSA…) | |
|---|---|---|
| Purpose | Represent binary data as text | Hide data from unauthorised parties |
| Key required? | No | Yes |
| Reversible by anyone? | Yes — one function call | Only with the correct key |
| Adds confidentiality? | No | Yes (when used correctly) |
| Output size | ~33% larger than input | Varies by algorithm |
Encryption transforms data using a key so that without the key, recovering the original is computationally infeasible. Base64 encoding has no such property.
Real-World Security Mistakes
Storing secrets "encoded" in source code
// ❌ This is not secret — anyone can decode it
const API_KEY = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");
// Anyone who reads this code runs:
atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=") // → "sk-live-abc123456789"Automated secret-scanning tools (GitHub's secret scanning, GitGuardian, truffleHog) all decode Base64 strings as part of their detection. If you're hoping Base64 stops them, it doesn't.
HTTP Basic Authentication
// The Authorization header looks like this:
Authorization: Basic dXNlcjpwYXNzd29yZA==
// That Base64 string decodes trivially:
atob("dXNlcjpwYXNzd29yZA==") // → "user:password"HTTP Basic Auth is only safe over HTTPS — the TLS layer provides the actual encryption. The Base64 in the header is purely for encoding the username:password pair as a single text value, not to hide it. Over plain HTTP, anyone intercepting the traffic can read the credentials immediately.
JWT "tokens" treated as secrets
// A JWT looks like three Base64url sections joined by dots:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.abc123
// The middle section (payload) decodes to:
atob("eyJ1c2VySWQiOjQyfQ") // → '{"userId":42}'JWT payloads are not encrypted — they are Base64url-encoded and signed. Anyone who gets the token can read the payload. The signature prevents tampering, but provides no confidentiality. Never put sensitive data (passwords, credit card numbers, personal info) in a JWT payload unless you're using JWE (JSON Web Encryption), which is a different and much less common standard.
Obfuscating data in URLs
// Common pattern in legacy apps
/profile?data=eyJ1c2VySWQiOjQyfQ==
// Paste into any decoder:
atob("eyJ1c2VySWQiOjQyfQ==") // → '{"userId":42}' Base64 in URLs adds zero security. It also breaks easily — the + and / characters in standard Base64 are not URL-safe and must be percent-encoded, which is why Base64url (+ → -, / → _, no padding) exists as a separate variant.
What Base64 Is Actually For
Base64 has legitimate, important uses — just not for security:
- Embedding binary data in text formats — images, fonts, and files embedded as
data:URIs in HTML/CSS, or attached to JSON API responses - Email attachments — MIME encoding uses Base64 to transmit binary files over protocols that only handle 7-bit ASCII
- Transport safety — some channels corrupt binary data (null bytes, control characters); Base64 guarantees the content survives intact
- Opaque identifiers — encoding an ID or cursor as Base64 signals "don't try to parse this" to API consumers, even though it provides no security
What to Use When You Actually Need Security
If your goal is to prevent unauthorised access to data, use a real cryptographic tool:
| Goal | What to use |
|---|---|
| Encrypt data at rest | AES-256-GCM (symmetric encryption) |
| Encrypt data in transit | TLS 1.3 (HTTPS) |
| Hash passwords | bcrypt, scrypt, or Argon2 — never SHA-256 alone |
| Sign tokens | HMAC-SHA256 (as in JWT HS256) or RS256 |
| Store secrets | Environment variables, a secrets manager (Vault, AWS Secrets Manager) |
| Encrypt tokens end-to-end | JWE (JSON Web Encryption) — not plain JWT |
When You Actually Need an Encrypted Token: JWE and JOSE
A standard JWT is signed (JWS), not encrypted — the payload is readable. If you genuinely need confidentiality of the claims, use a JWE (JSON Web Encryption, RFC 7516) instead. JWE is part of the JOSE family of standards:
- JOSE algorithms — content encryption (e.g.
A256GCM) and key management (e.g.RSA-OAEP-256,ECDH-ES,A256KW). The "alg" picks how the content-encryption key is wrapped; "enc" picks how the payload itself is encrypted. - Key wrapping — JWE generates a fresh per-message content-encryption key and wraps it under the recipient's key (asymmetric or symmetric). The wrapped key travels in the token header. This lets the same plaintext encrypt to different recipients without re-encrypting the body.
- JWE compact form — five Base64url sections joined by dots (header.key.iv.ciphertext.tag), visually like a JWT but four pieces instead of three.
Pick a vetted library (jose, node-jose, python-jose) rather than rolling JWE yourself — the combinations of algorithms and key formats are subtle, and silent fallback to weak algorithms is a classic JOSE bug.
A Quick Test for Your Own Code
Search your codebase for btoa(, atob(, and Buffer.from(…, 'base64'). For each occurrence, ask:
- Am I doing this to make text fit in a field or survive transport? → Fine.
- Am I doing this to hide data from someone? → Replace with real encryption or access control.
Frequently Asked Questions
Is Base64 encryption?
No. Base64 is a reversible encoding with no key — anyone can decode it in one function call (atob()). Encryption requires a key and makes recovery infeasible without it.
Is Base64 secure for storing passwords or API keys?
No. Decoding is trivial and secret-scanners decode Base64 automatically. Use a secrets manager or environment variables, and hash passwords with bcrypt, scrypt, or Argon2.
Can anyone read a JWT payload?
Yes. JWT payloads are Base64url-encoded and signed, not encrypted — anyone with the token can read every claim. Never put secrets in a JWT unless you use JWE. Pasting tokens into online tools is risky; see why you shouldn't paste sensitive JSON online.
What is Base64 actually for?
Representing binary data as text safely — data: URIs, email (MIME) attachments, and transport over channels that only handle ASCII. It's a wrapper, not a security measure.
Encode and Decode in Your Browser
Need to inspect a Base64 string right now? Base64 Encode & Decode on fixjson.org lets you encode or decode any string instantly — including full Unicode and UTF-8 — without installing anything. Useful for debugging JWT payloads, inspecting API responses, or checking what's hiding inside a data: URI.
- Base64 Encode & Decode — instant, in-browser, no data sent to any server
- RFC 4648: The Base64 Standard — the history and formal definition of Base64 and Base64url
- How to Decode a JWT — read a token's claims, and why decoding isn't verifying
- RFC 7519: JSON Web Token (JWT) — how Base64url is used in the JWT standard, and the security considerations around JWT payloads
- URL Decode — percent-decode a URL or query string
- JSON Stringify — encode a JSON value as an escaped string literal