← back to blog
4 min read
  • #base64
  • #encoding
  • #security
  • #web-dev

Base64 is not encryption

Base64 reverses in one line with no key. Here is what encoding is actually for, how to decode it on sight, and the ways people get burned treating it as protection.


Base64 looks scrambled, so people treat it like a lock. It is not a lock. It is a costume. Anyone can reverse it in one line with no key, and I still keep running into passwords and API keys that were "protected" this way.

What Base64 actually is

Encoding and encryption solve different problems. Encoding changes how data is written down so it survives the trip. Encryption changes who can read it. Base64 is encoding, full stop.

It exists because a lot of systems only handle plain text. Email was built for text. JSON is text. So when you need to move binary data, like an image or a random byte string, through a text-only pipe, you translate it into 64 safe characters: A to Z, a to z, 0 to 9, plus + and /, with = as padding on the end.

You already use it every day without noticing:

  • Email attachments (that is what MIME does under the hood)
  • Images inlined in HTML or CSS as data URIs
  • JWT tokens, which are three Base64 chunks joined by dots
  • The Authorization header in HTTP Basic auth
  • Binary blobs stuffed into JSON or env vars

The tradeoff is size. Base64 output is about 33 percent bigger than the input, so it is not compression either. It is a shipping container. Anyone can open a shipping container.

How to spot it and decode it

Signs a string is Base64:

  • Only letters, digits, +, and /
  • Length is a multiple of 4
  • One or two = signs at the end
  • It starts with eyJ. That is {" in disguise, which means a Base64 encoded JSON object, which almost always means a JWT.

Take this string: SGVsbG8sIEhlYmVyIENpdHk=

Decode it any of these ways:

  • Browser console: atob('SGVsbG8sIEhlYmVyIENpdHk=')
  • Linux or Mac: echo 'SGVsbG8sIEhlYmVyIENpdHk=' | base64 -d
  • PowerShell: [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('...'))

You get "Hello, Heber City". No key, no password, no math degree. That is the whole point of this post. If you can decode it that fast, so can anyone who finds your data.

Same trick works on a JWT. The header eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 decodes to {"alg":"HS256","typ":"JWT"}.

The mistakes that actually hurt

Basic auth over plain HTTP. The header Authorization: Basic ZGVtbzpodW50ZXIy is just demo:hunter2 run through Base64. Over HTTPS that is tolerable, because the whole request is encrypted in transit. Over plain HTTP it is a password broadcast with extra steps.

API keys "hidden" in frontend JavaScript. I have run into sites where a paid API key sits in the page source, Base64 encoded, presumably because it looked safer that way. View source, decode, done. If it ships to the browser, it is public. A key on a small salon site is exactly as exposed as one on a bank site.

Treating JWT payloads as private. The signature on a JWT stops tampering. It does not stop reading. Anyone holding the token can decode every claim inside it. Do not put anything in a JWT you would not print on a receipt.

"Encrypted" database columns that are just Base64. If the table leaks, decoding is instant and scriptable. Ten thousand rows take about a second.

Missing it as a defender. Attackers love Base64 because it slips past naive filters. Obfuscated PowerShell has an -EncodedCommand flag that takes Base64 directly. If you only search logs for plain-text bad strings, you miss the encoded copy. Decoding suspicious strings needs to become a reflex.

This exact distinction, encoding versus encryption versus hashing, gets drilled early in the Security+ SY0-701 material I am studying right now. I am not certified yet, but you do not need a cert to get this one right. Hashing, for the record, is also not encryption. It is one-way on purpose, which makes it right for passwords and wrong for anything you need back.

What to use instead

  • Keep secrets on the server. Environment variables or a secrets manager, never in shipped frontend code, encoded or not.
  • Need confidentiality? Use real encryption through a vetted library, AES-256-GCM or libsodium defaults. Do not invent your own.
  • Storing passwords? Hash them with bcrypt or argon2.
  • Moving data? TLS everywhere. It is free with Let's Encrypt and most hosts turn it on by default now.

And keep using Base64 for what it is good at, which is moving bytes through text. The tool is fine. The expectation is the bug.

I decode Base64 often enough while studying that I built an encoder and decoder into my own app, sitting next to the JWT inspector and the Security+ flashcards I drill every day. If you want the same kit, it is Aldo's Toolkit, free on both stores. No ads, no tracking, works offline.

$ share

community rating

$ ls ./comments

sign in or create an account to rate and comment.

no comments yet, be first.