Base64 isn't encryption — five places engineers still confuse them
The first rule, written on every base64 tutorial since 2003: base64 is encoding, not encryption. Anyone with a browser console can decode it.
The first rule is right. And yet I keep finding production systems built on the assumption that base64 hides something. Five common varieties.
1. “Storing the API key in base64” in client-side code
const API_KEY = atob("c2tfbGl2ZV81MTIzNGFiY2Q="); // sk_live_51234abcd
fetch("https://api.example.com", { headers: { Authorization: `Bearer ${API_KEY}` } });
This was meant to “obfuscate” the key from someone scrolling the bundled
JS. It does nothing. The decoded string ends up in network requests
anyway, the source map ships the original, and atob is searchable.
Fix: client-side code can never hold a secret. Move the API call to a server you control, or use a key system designed for client exposure (scoped, short-lived, low-trust).
2. Kubernetes secrets
apiVersion: v1
kind: Secret
metadata:
name: db-password
data:
password: c3VwZXJzZWNyZXQxMjM= # superscret123
K8s docs are explicit that this is base64, not encrypted. But the
data: field plus the format (“Secret”) gives many teams the wrong
impression. Anyone with read access to the namespace’s Secret resource
can decode it. It’s not stored encrypted at rest by default — that
requires enabling KMS encryption explicitly.
Fix: enable etcd encryption, restrict RBAC on secrets, and treat
“can read secrets” as equivalent to “knows the value.” For higher
sensitivity, use an external secret manager (Vault, AWS Secrets
Manager) with the K8s integration.
(I wrote a whole separate post on this if you want the deeper version.)
3. JWT payload “encryption”
If you’re working with JWTs regularly, the JWT decoder at jwt.tooljo.com shows exactly what the base64url-encoded segments contain — header, payload, and signature bytes — so you can see the “this isn’t private” property with your own tokens.
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWxpY2UifQ.signature
The middle segment is a base64url-encoded JSON payload. It is not encrypted. Anyone holding the token can decode the claims. The signature prevents tampering, not snooping.
War story: a SaaS put is_admin: true and the user’s home address in
the JWT payload. The frontend used the address for a “location-based
greeting.” A bug-bounty researcher noticed and reported it — to the
SaaS’s credit, they fixed it within a day.
Fix: JWT claims should be public information. If you need to hide a value, use JWE (JSON Web Encryption) — but better, just don’t put secret data in tokens. Reference it by ID and look it up server-side.
4. Email tracking pixels with “encrypted” base64 IDs
https://email.example.com/track?id=dXNlcl8xMjM0NQ==
Base64-encoded user_12345. The marketer thought id=dXNlcl8xMjM0NQ==
was less guessable than id=user_12345. It isn’t. The encoding is
trivial, and the next user is user_12346 — so anyone who decodes
one ID can enumerate the rest.
Fix: use opaque, random IDs (UUID v4, or a HMAC of the underlying ID). Don’t conflate “looks scrambled” with “actually unguessable.”
5. Storing PII as base64 in URLs
https://example.com/onboarding?u=eyJlbWFpbCI6ImFsaWNlQGV4YW1wbGUuY29tIn0=
Base64-encoded {"email":"alice@example.com"}. Someone shared the link
on Slack; the email got logged in Slack’s URL preview service, in the
recipient’s CDN logs, and in the recipient’s browser history.
Fix: PII should not appear in URLs at all — they’re cached, logged, and shared. Use POST bodies for sensitive data, or one-time-use signed tokens that don’t reveal the underlying data.
When base64 is the right tool
- Embedding binary in text: data URIs, MIME, JSON-with-blobs. Base64 was designed for this.
- JWT payload encoding: the spec mandates base64url here. Just don’t expect privacy.
- HTTP Basic Auth:
Authorization: Basic <base64(user:pass)>— this is the spec, and HTTPS provides the privacy. - Wire formats: anywhere a system wants ASCII bytes but you have binary data.
The encoding-vs-encryption test
Two quick checks:
- Can someone with the encoded data and zero secrets recover the original? If yes (base64, hex, URL-encoding), it’s encoding.
- Does the operation require a key? If yes, it’s encryption (or a keyed hash like HMAC).
If the answer to #1 is yes, the data isn’t private. The data is transportable. That’s a different property and a different threat model.
The base64 tool is for the encoding case — making binary transportable. For the encryption case, you want libsodium, AGE, or your platform’s KMS, not a text encoder. And for the integrity case (detect tampering, but not encrypt), reach for a real hash function — hash.tooljo.com covers MD5/SHA-1/SHA-256/SHA-512 side-by-side. The decision tree for which one to use is at hash.tooljo.com/which-hash-function.