Base64 Encoding Explained — How It Works, Variants, and When to Use It

Base64 is the most widely used binary-to-text encoding scheme on the internet. It turns raw bytes into printable ASCII characters, making binary data safe for transport through text-only channels: email attachments (MIME), JSON Web Tokens, data URIs in CSS/HTML, X.509 certificates (PEM), and cryptographic key exchange. This guide explains exactly how Base64 works, the three main variants, and when to use each.

1. Why Base64 Exists

Many internet protocols — SMTP (email), HTTP headers, JSON, XML — were designed for human-readable text. Sending raw binary through these channels corrupts data because:

Base64 solves this by converting any binary data into a safe subset of 64 ASCII characters: A-Z, a-z, 0-9, +, /, and = for padding.

Key insight: Base64 is not encryption or compression. It's a reversible encoding that expands data by ~33%. Anyone can decode it back to the original bytes.

2. How the Algorithm Works

Base64 processes input in 3-byte (24-bit) groups and outputs 4 characters per group:

Input:   M         a         n         (3 bytes = 24 bits)
ASCII:   0x4D      0x61      0x6E
Binary:  01001101  01100001  01101110

Split into 4 groups of 6 bits:
         010011  010110  000101  101110
Decimal:   19      22      5       46
Base64:    T       W       F       u
Result: "TWFu"

Step-by-Step Algorithm

  1. Group bytes: Take 3 bytes (24 bits) at a time. If fewer than 3 bytes remain, pad with zeros.
  2. Split into 6-bit chunks: Divide the 24 bits into four 6-bit values (0–63).
  3. Map to Base64 alphabet: Use the lookup table — 0→A, 1→B, ..., 25→Z, 26→a, ..., 51→z, 52→0, ..., 61→9, 62→+, 63→/.
  4. Add padding: If input had 1 extra byte → output 2 chars + ==. If 2 extra bytes → output 3 chars + =.

The Base64 Alphabet

ValueCharValueCharValueCharValueChar
0–25A–Z26–51a–z52–610–962+
63/(padding) =

3. Three Base64 Variants

3.1 Standard Base64 (RFC 4648 Section 4)

The original encoding. Uses + and / as characters 62 and 63, with = padding. This is what btoa() produces in JavaScript and base64.b64encode() in Python.

Input:  "Hello!"
Output: "SGVsbG8h"

3.2 URL-Safe Base64 (base64url, RFC 4648 Section 5)

Replaces + with - and / with _ so the output is safe in URLs, filenames, and path segments. Padding (=) is often omitted because it conflicts with URL query parameter syntax.

Used in: JWT tokens, JWS/JWE signatures, OAuth 2.0 PKCE code verifiers, FIDO2 WebAuthn challenge strings.

Standard: "a+b/c==de"
URL-safe: "a-b_cde"      (padding stripped)

// JWT example (period-separated):
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.dYBjZo3v

3.3 MIME Base64 (RFC 2045)

For email attachments and multipart MIME messages. Wraps output at 76 characters per line using \r\n line breaks. Uses the same alphabet as Standard Base64.

Used in: Email attachments, SOAP messages with binary content, embedding images in HTML emails.

4. Padding: Why = and ==?

The = character is not part of the 64-character alphabet — it signals how many padding bytes were added:

Input LengthOutput CharsPaddingExample
Multiple of 3 (e.g., 6 bytes)8 charsNoneSGVsbG8h
1 extra byte (e.g., 4 bytes)6 chars==SGVsbA==
2 extra bytes (e.g., 5 bytes)7 chars=SGVsbG8=

Many Base64 decoders tolerate missing padding. URL-safe Base64 often omits it entirely since the output length is determined by the surrounding protocol (e.g., a JWT payload always has a known byte length from the JSON structure).

Rule of thumb: Use padding for stored data (PEM files, database columns). Omit padding for URL/token use (JWTs, query params, path segments).

5. Common Use Cases

5.1 Data URIs (Embedding Binary in HTML/CSS)

Inline images, fonts, and other binary assets directly in HTML or CSS without separate HTTP requests.

<img src="data:image/png;base64,iVBORw0KGgoAAAANS...">

/* CSS background */
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...);

5.2 JSON Web Tokens (JWT)

JWTs use URL-safe Base64 without padding for header, payload, and signature segments.

// JWT structure:
base64url(header).base64url(payload).base64url(signature)

5.3 PEM Certificate Format

X.509 certificates, private keys, and CSRs use Base64 with specific begin/end markers and line wrapping (64 chars per line).

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKLp5n6G7u0UMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
...
-----END CERTIFICATE-----

5.4 Email MIME Attachments

Binary attachments (PDFs, images, ZIP files) are Base64-encoded within MIME multipart messages.

Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"

JVBERi0xLjQKJeLjz9MKNCAwIG9iago8PC9UeXBlL1hSZWZ1bm...

5.5 Binary Keys in JSON

When an API needs to transmit raw cryptographic keys or binary metadata inside JSON, Base64 is the standard container.

{
  "kty": "EC",
  "x": "base64url-encoded-x-coordinate",
  "y": "base64url-encoded-y-coordinate"
}

6. Base64 vs Hex Encoding

PropertyBase64Hex
Expansion ratio4:3 (33% overhead)2:1 (100% overhead)
Character set64 chars (A-Za-z0-9+/)16 chars (0-9A-F)
ReadabilityLow (case-sensitive, symbols)Medium (fixed-width per byte)
Best forLarge binary: images, files, certificatesSmall values: hashes, keys, APDUs

For debugging binary protocols like APDUs or EMV card data, hex is preferred because each byte maps to exactly 2 characters. For embedding files or images, Base64 wins with 33% less overhead.

7. Try It Yourself

Use our free online tool to encode and decode Base64 in all three variants — Standard, URL-safe, and MIME:

Open Base64 Encoder/Decoder →

8. Quick Reference — Code Snippets

JavaScript

// Standard Base64
const encoded = btoa("Hello, World!");            // "SGVsbG8sIFdvcmxkIQ=="
const decoded = atob(encoded);

// URL-safe Base64
function toBase64url(buf) {
  return btoa(String.fromCharCode(...new Uint8Array(buf)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// Handle Unicode strings (TextEncoder handles multi-byte)
function utf8ToBase64(str) {
  const bytes = new TextEncoder().encode(str);
  return btoa(String.fromCharCode(...bytes));
}

Python

import base64

# Standard
encoded = base64.b64encode(b"Hello, World!").decode()  # "SGVsbG8sIFdvcmxkIQ=="
decoded = base64.b64decode(encoded)

# URL-safe
encoded = base64.urlsafe_b64encode(data).decode().rstrip('=')

# MIME (with line wrapping)
encoded = base64.encodebytes(data).decode()