·7 min read·Blog

Base64 Is Not Encryption: The Security Mistake I See in Code Reviews Every Week

Base64-encoded strings look scrambled, which trips up a surprising number of developers into treating them as protected data. Here's what Base64 actually does, where it legitimately belongs, and what to reach for when you need data that's actually secure.

The code review that started this

I was reviewing a pull request for a small internal tool. The developer had stored an API key in a config file. To "protect" it, they had Base64-encoded the key and stored the encoded version. The comment in the code said: encoded for security.

The encoded string looked like c2VjcmV0X2tleV8xMjM0NQ==. It took me four seconds to decode it back to secret_key_12345using the browser. The "protection" was zero. Anyone who found the config file could read the key in the same four seconds.

This pattern — using Base64 as if it were a security layer — is more common than I expected when I started doing code reviews regularly. I see it with API keys, with passwords, with internal URLs that someone wanted to "hide," and occasionally with entire JSON payloads containing sensitive user data.

What Base64 actually is

Base64 is an encoding scheme, not an encryption scheme. The distinction is fundamental:

  • Encoding transforms data from one representation to another. It is reversible by anyone with knowledge of the scheme. There is no secret key. Anyone who knows the scheme (Base64 is public and standardized) can decode the data.
  • Encryption transforms data in a way that can only be reversed with a secret key. Without the key, the data is unrecoverable (if the encryption is strong).

Base64 was invented to solve a specific problem: binary data can't always be safely transmitted through systems designed for text (email, HTTP headers, URLs). Base64 converts binary bytes into a subset of 64 ASCII characters (A-Z, a-z, 0-9, +, /) that are safe in any text context. The tradeoff is that encoded data is about 33% larger than the original.

That's the whole story. Base64 was never designed to hide data. It makes binary data text-safe. It makes text look unfamiliar. It does not protect anything.

Where Base64 actually belongs

Base64 is a useful and correct tool in specific situations:

  • Embedding binary data in JSON or HTML. If you want to embed an image in a JSON API response, you Base64-encode the binary image bytes so they can sit in a JSON string field. Same for embedding a font in CSS via data: URLs.
  • HTTP Basic Authentication headers. The HTTP spec defines Basic Auth as the username:password string encoded in Base64 and sent in the Authorization: Basic header. This is for format compatibility, not security. Basic Auth is only safe over HTTPS, which provides the actual encryption.
  • JWT tokens.A JSON Web Token is three Base64url-encoded chunks (header, payload, signature) joined by dots. The header and payload are not encrypted — they're just encoded, and anyone can read them. The security in a JWT comes from the signature, which proves the token hasn't been tampered with.
  • Email attachments. MIME email attachments are Base64-encoded so binary files can travel through the text-based email infrastructure.

How to decode Base64 in 10 seconds

To illustrate how trivial decoding is, here are three ways to decode any Base64 string immediately:

  • Browser console: Open DevTools (F12), go to the Console tab, and type atob('your-base64-string-here'). The decoded string appears instantly. atob() is a built-in browser function. No library needed.
  • Browser tool: Paste it here and click Decode. Done in two clicks.
  • Terminal: echo 'your-base64-string' | base64 -d on macOS/Linux.

There is nothing to crack. There is no password to guess. Base64 decoding is not reversing encryption — it's applying a publicly documented lookup table.

What to use instead

The right tool depends on what you actually need:

Storing a secret (API key, password, token) in a config file or database: Don't store it in plaintext in source code at all. Use environment variables (process.env.API_KEY) and inject them at runtime. For production secrets, use a secrets manager: AWS Secrets Manager, HashiCorp Vault, Vercel environment variables, or similar. These systems handle access control, rotation, and audit logs.

Storing a user password in a database: Hash it with a slow hashing algorithm. bcrypt, scrypt, or Argon2id are the current standards. Never store plaintext or Base64-encoded passwords. The bcrypt tool on this site lets you hash and compare passwords in the browser to understand how the output looks. A bcrypt hash includes the salt and cost factor — the hash is deliberately slow to compute so brute-force attacks are impractical.

Sending sensitive data over a network:Use HTTPS (TLS). The transport layer handles encryption. You don't need to encrypt the data yourself before sending it over HTTPS, though you may want to for defense-in-depth.

Encrypting data at rest: Use AES-256 (symmetric) or RSA/ECDSA (asymmetric) depending on whether you need one or two keys. The Web Crypto API (window.crypto.subtle) provides browser-native AES-GCM encryption without any library.

The URL-safe variant (Base64url)

Standard Base64 uses + and / characters, which have special meaning in URLs (+ means space in query strings, / is a path separator). Base64url replaces these with - and _, and drops the = padding. This is the variant used in JWT tokens and many modern APIs.

If you try to decode a Base64url string with atob() in the browser, it will fail because of the character difference. The fix is to replace - with + and _ with / before decoding. Or use the base64 tool, which handles both variants automatically.

The SHA-256 confusion

A related misconception: SHA-256 hashes are sometimes displayed as Base64, and developers occasionally confuse the Base64 encoding with the security property. The security comes from SHA-256 (a one-way cryptographic hash), not from the Base64 display format. The same hash displayed in hexadecimal is equally secure — or equally insecure if used incorrectly.

SHA-256 is appropriate for verifying file integrity and signing tokens. It is notappropriate for hashing passwords (it's too fast — purpose-built password hashing functions are intentionally slow). Use the hash generator to see what SHA-256, SHA-512, and MD5 output looks like for a given input.

What to actually do when you see this in a codebase

If you find Base64-encoded sensitive data in a codebase you're reviewing:

  1. Decode it to verify what's actually stored (you can do this in the browser console).
  2. Treat the underlying secret as compromised if it's been in any shared repository, even a private one with multiple contributors. Rotate it.
  3. Move the secret to environment variables or a secrets manager before the next deploy.
  4. Check git history — git log -p --all -S 'the-base64-string' — to see how far back the exposure goes. If it was committed, the history persists even after deletion.

Related tools

  • Base64 encoder/decoder — encode or decode Base64 and Base64url strings locally in your browser.
  • Hash generator — generate MD5, SHA-1, SHA-256, SHA-512 hashes of any string or file.
  • Bcrypt tool — hash and compare passwords using the bcrypt algorithm.
  • JWT decoder — decode and inspect any JSON Web Token (header, payload, expiry) without sending it anywhere.

Written by Achraf A., founder of TheFreeAITools — built in Morocco. The code review incident described above happened in early 2025 on a real project; details changed to protect the team.

☕ Support Us