← back to blog
security 6 min read May 2026

Base64 Is Not Encryption: A Mistake That Exposes Your Data

Base64 shows up everywhere in web development — JWT payloads, data URIs, MIME email attachments, HTTP Basic Authentication headers. It looks like gibberish, and that appearance fools developers into treating it as a security measure. It is not. Base64 is a completely reversible encoding that any developer can decode in seconds, with no key, no password, and no special tools.

What Base64 actually does

Base64 is a binary-to-text encoding scheme. Its purpose is to represent arbitrary binary data using only printable ASCII characters. The 64 characters used are A–Z, a–z, 0–9, +, and / (with = as padding).

The algorithm is simple: take every 3 bytes of input, treat them as a 24-bit number, split it into four 6-bit groups, and map each group to one of the 64 characters. That's it. No keys, no secrets, no complexity. The same input always produces the same output. The output can always be reversed to the original input.

# Encoding "Hello, World!" → SGVsbG8sIFdvcmxkIQ== # Decoding — anyone can do this instantly SGVsbG8sIFdvcmxkIQ== → "Hello, World!"

In JavaScript: atob("SGVsbG8sIFdvcmxkIQ==") returns "Hello, World!". In Python: base64.b64decode("SGVsbG8sIFdvcmxkIQ=="). In a terminal: echo "SGVsbG8sIFdvcmxkIQ==" | base64 -d. Every programming language, every operating system, every browser has built-in Base64 decoding.

Why developers confuse it with encryption

The confusion is understandable. Base64 output looks random to the untrained eye. A string like dXNlcjpwYXNzd29yZA== doesn't obviously reveal that it's the username user and password password separated by a colon — but decoding it takes one line of code and reveals exactly that.

Several common web standards use Base64 in security-adjacent contexts, which reinforces the confusion:

Common mistake: "We store user data in Base64 in the database so it's not readable." It is readable. Any engineer with database access — or any attacker who gains it — can decode every record in milliseconds. This provides zero security while adding complexity.

Real examples of what gets exposed

API keys in configuration files

Teams sometimes Base64-encode API keys in config files or environment variables under the assumption that this obscures them. A Base64-encoded API key in a leaked config file is as dangerous as a plaintext one — it takes five seconds to decode. This provides no protection against accidental commits, log leaks, or insider threats.

Passwords in authentication tokens

Some legacy authentication systems encode credentials in Base64 and store them in cookies or localStorage. The user's password (or a hash of it) is visible to any JavaScript on the page, any proxy in the network path, and anyone who inspects the cookie. HTTP Basic Auth is the canonical example of this pattern — which is why it's only acceptable over HTTPS.

Sensitive data in JWT payloads

JWT payloads are Base64URL-encoded. A token containing a user's email, phone number, SSN, or medical data is exposing that data to anyone who intercepts the token or reads it from storage. Paste any JWT into the decoder on this site and you'll see exactly what's in it — no secret needed.

When Base64 is the right tool

Base64 is a solution to a specific problem: transmitting binary data over channels that only support text. It's the right choice when:

Notice what's absent from this list: protecting, obscuring, or encrypting data. Base64 is a format transformation, not a security mechanism.

What actual encryption looks like

Encryption takes plaintext and a key, and produces ciphertext that is mathematically infeasible to reverse without the key. Modern symmetric encryption (AES-256-GCM) produces output that looks similar to Base64 but requires a 256-bit key to decrypt. Without the key, the ciphertext is computationally unbreakable.

For data at rest (stored in a database), use a proper encryption library:

# Python — encrypting with AES-256-GCM (using cryptography library) from cryptography.hazmat.primitives.ciphers.aead import AESGCM import os key = AESGCM.generate_key(bit_length=256) aesgcm = AESGCM(key) nonce = os.urandom(12) ciphertext = aesgcm.encrypt(nonce, b"sensitive data", None) # ciphertext is unreadable without key + nonce

For data in transit, TLS (HTTPS) handles encryption at the transport layer — you don't need to encrypt application data manually when the connection is already encrypted.

A common pattern is to encrypt sensitive data with AES, then Base64-encode the binary ciphertext so it can be stored as text. In this case, Base64 is doing its proper job (format conversion) while AES does the actual security work.

Identifying Base64 in the wild

Base64 strings are recognizable by their character set (A–Z, a–z, 0–9, +, /) and trailing = padding. URL-safe Base64 (used in JWTs) replaces + with - and / with _. If you see these patterns in a codebase treating them as secret or protected, that's a red flag worth investigating.

// summary