Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Base64 is encoding, not encryption

On this page
  1. What base64 actually does
  2. Confusion #1 — “I base64’d the password before storing it”
  3. Confusion #2 — “I base64’d the API key in the env var”
  4. Confusion #3 — “I base64’d the cookie value to obscure it”
  5. Confusion #4 — “I base64’d the file to make it private”
  6. Confusion #5 — “I base64’d the JSON before sending it over HTTPS”
  7. When base64 is the right answer
  8. The mental model
  9. Try it

Base64 is a way to represent arbitrary bytes using only 64 ASCII characters. Anyone with the encoded string can recover the original bytes in microseconds, with no key, on any computer. There is nothing secret about a base64 string.

This is obvious to anyone who’s read the spec. It is somehow not obvious in production. Here are five places engineers confuse encoding with encryption — and what each costs.

What base64 actually does

Base64 takes 3 bytes of input and produces 4 ASCII characters of output. It uses A–Z, a–z, 0–9, +, and /, plus = for padding. The URL-safe variant swaps + and / for - and _. That’s all of it.

Decoding is the inverse: 4 characters back into 3 bytes. No key. No choice. The mapping is fixed in the RFC.

The use cases base64 is correct for:

In every one of those, base64 is making bytes transportable over a text-only medium. It is not making them private.

Confusion #1 — “I base64’d the password before storing it”

The intent is “I made the password not look like the password”. The result: when an attacker dumps the database, they have a base64 string, which they decode in 100 milliseconds, and now they have the password.

What you wanted is hashing with a slow, salted algorithm — bcrypt, scrypt, or Argon2id. The hash is one-way (you can’t recover the password from it) and has a built-in cost factor (you can tune how slow verification is, to thwart brute force).

Cost: every user’s password leaks the day the database leaks. Often this is also a regulatory disaster — base64 storage of passwords typically violates SOC 2, PCI DSS, and GDPR’s “appropriate technical measures” clause.

Fix:

DON'T: stored = base64(password)
DO:    stored = argon2id(password, salt = random32bytes(), m=64MB, t=3, p=4)

Hash the password. Store the hash. Throw away the plaintext.

Confusion #2 — “I base64’d the API key in the env var”

Same energy as #1. The intent is “this string in .env looks more secure”. The result: anyone with read access to the file (which is everyone with shell access to the box) decodes it in milliseconds.

What you wanted is to store the key in a secret manager — Vault, AWS Secrets Manager, GCP Secret Manager, 1Password CLI, Doppler — and have your app fetch it at boot. The secret manager handles encryption at rest, access control, audit logs, and rotation.

If a secret manager is overkill for your scale, the next-best option is to encrypt the env file itself with sops or age and decrypt on deploy.

Cost: a leaked env file = leaked credentials, full stop.

Cookies are text-only, so to put non-ASCII bytes in one, base64 is appropriate. But teams sometimes base64 a JSON cookie value (base64({user_id: 42, role: "admin"})) on the assumption that “users won’t look inside”.

Users do look inside. A browser extension shows cookie values formatted, decoded, base64-decoded automatically. A determined user edits the cookie to {user_id: 1, role: "admin"}, re-encodes, sends it, and is now logged in as user 1.

What you wanted is a signed cookie (HMAC-signed) or a session ID that points at a server-side session. The signed cookie pattern is what JWT does for the same use case — the signature is the security primitive, not the encoding.

Cost: privilege escalation by anyone who reads the docs.

Confusion #4 — “I base64’d the file to make it private”

Showing up in the wild: SaaS tools that store user uploads as base64 in a database column “for privacy”. Or apps that base64-encode a file before storing it on disk to “obfuscate” it.

The base64 string is the file. Anyone who can read the column or the file has the file. Encoding is not a permission system.

What you wanted: file storage with access controls (S3 bucket policies, presigned URLs, server-side encryption with a KMS key) and actual encryption at rest if the data is sensitive.

Cost: false sense of security; same data exposure as plaintext.

Confusion #5 — “I base64’d the JSON before sending it over HTTPS”

The HTTPS layer already encrypts everything in transit. Base64-encoding the body adds 33% to the payload size and zero security. (33% because base64 represents 3 bytes as 4 characters.)

The exception: HTTP headers can’t carry arbitrary bytes, so a binary header must be base64-encoded. That’s a transport-layer requirement, not a security one.

Cost: bandwidth. On hot APIs, the 33% bloat shows up in egress bills and TTFB.

When base64 is the right answer

To be clear — base64 is correct, even essential, in many places:

In every one of those: the security comes from somewhere else (HTTPS for confidentiality, HMAC for integrity, the hash function itself for authenticity). Base64 is just the envelope.

The mental model

Encoding is how the data looks on the wire. Encryption is whether anyone except the intended recipient can read it. They are orthogonal concerns. A message can be:

Picking the wrong one for the wrong reason is what produces the bugs above.

Try it

Encode and decode at base64.tooljo.com — both standard and URL-safe variants, plus file-to-data-URI for the common image case. It’s a static page, so nothing you paste leaves your browser.