What Is Base64 and Why Does It Exist?
Base64 is a binary-to-text encoding scheme defined in RFC 4648 (October 2006). It was designed to solve a fundamental problem: many text-based protocols (SMTP email, HTTP headers, XML, JSON) can only safely carry a subset of ASCII characters. Raw binary data contains bytes in the range 0x00–0xFF, many of which are control characters that will corrupt or truncate a text message.
Base64 solves this by converting arbitrary binary data into a string using only 64 safe characters: A–Z (26), a–z (26), 0–9 (10), + and / (2), plus = for padding. Every byte value 0x00–0xFF can be represented unambiguously using these characters.
The trade-off is size: Base64-encoded data is approximately 33% larger than the original binary. Three bytes (24 bits) are encoded as four characters (24 bits / 6 bits per character = 4 characters).
How Base64 Encoding Works — Step by Step
Base64 encodes input in 3-byte groups. Each group of 3 bytes (24 bits) is split into four 6-bit values, each of which maps to one character in the Base64 alphabet.
Input: M a n
ASCII: 77 97 110
Binary: 01001101 01100001 01101110
↓ Split into 6-bit groups ↓
010011 010110 000101 101110
19 22 5 46
↓ Map to Base64 alphabet ↓
T W F u
Result: "TWFu"
When the input length is not a multiple of 3, padding is applied:
- 1 leftover byte → two Base64 chars +
== - 2 leftover bytes → three Base64 chars +
= - 0 leftover bytes → no padding
Example: "Ma" encodes to "TWE=" (one = padding because 2 bytes were left over).
Standard Base64 vs Base64URL — When to Use Each
RFC 4648 defines two distinct alphabets:
| Variant | Characters 62–63 | Padding |
|---|---|---|
| Standard (§4) | + and / |
Required (=) |
| URL-safe (§5) | - and _ |
Optional (often omitted) |
Standard Base64 uses + and /, which have special meanings in URLs and HTTP query strings. Always use Base64URL when embedding Base64 in a URL, query parameter, cookie, or JWT header/payload.
JWT tokens, for example, use Base64URL encoding without padding — that's why you won't see = signs or +// characters in a JWT.
The conversion between the two is trivial:
// Standard → URL-safe
standard.replace(/+/g, '-').replace(///g, '_').replace(/=/g, '')
// URL-safe → Standard (restore padding first)
const padded = urlSafe + '='.repeat((4 - urlSafe.length % 4) % 4)
padded.replace(/-/g, '+').replace(/_/g, '/')Where Base64 Appears in Real Systems
- HTTP Basic Authentication — credentials are sent as
Authorization: Basic base64(username:password). This is encoding, not encryption — always use HTTPS. - JSON Web Tokens — the header and payload are Base64URL-encoded; the signature is Base64URL-encoded binary.
- Data URIs — embed images and fonts directly in HTML/CSS:
data:image/png;base64,iVBORw0KGgo... - Email attachments (MIME) — defined in RFC 2045. Email was originally designed for 7-bit ASCII text; Base64 allows binary attachments to traverse legacy mail servers.
- API keys and secrets — many services distribute random bytes as Base64 strings because they are easier to copy/paste than raw hex.
- TLS/SSH certificates and keys — PEM format is a Base64-encoded DER certificate wrapped in
-----BEGIN CERTIFICATE-----headers. - Content Security Policy hashes — CSP script hashes use Base64-encoded SHA digests:
sha256-base64hash. - WebCrypto / crypto APIs — exchanging keys and signatures between browser WebCrypto and server-side libraries typically uses Base64 encoding.
Base64 in 6 Programming Languages
// Browser-native (ASCII only — breaks on non-Latin Unicode)
const encoded = btoa('Hello, World!'); // 'SGVsbG8sIFdvcmxkIQ=='
const decoded = atob('SGVsbG8sIFdvcmxkIQ=='); // 'Hello, World!'
// Safe UTF-8 encoding (handles all Unicode)
function encodeUTF8(str) {
return btoa(encodeURIComponent(str).replace(
/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16))
));
}
// Node.js (also works in browsers via Buffer polyfill)
const encoded = Buffer.from('Hello').toString('base64'); // 'SGVsbG8='
const decoded = Buffer.from('SGVsbG8=', 'base64').toString(); // 'Hello'
// URL-safe Base64 in Node.js
const urlSafe = Buffer.from('Hello').toString('base64url');import base64
# Standard Base64
encoded = base64.b64encode(b'Hello, World!')
# b'SGVsbG8sIFdvcmxkIQ=='
decoded = base64.b64decode(b'SGVsbG8sIFdvcmxkIQ==')
# b'Hello, World!'
# URL-safe Base64 (no + or /)
url_encoded = base64.urlsafe_b64encode(b'Hello, World!')
# Encode a string (not bytes)
text = 'Héllo'
encoded_str = base64.b64encode(text.encode('utf-8')).decode('ascii')
# Decode back to string
decoded_str = base64.b64decode(encoded_str).decode('utf-8')package main
import (
"encoding/base64"
"fmt"
)
func main() {
input := []byte("Hello, World!")
// Standard Base64
encoded := base64.StdEncoding.EncodeToString(input)
fmt.Println(encoded) // SGVsbG8sIFdvcmxkIQ==
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil { panic(err) }
fmt.Println(string(decoded)) // Hello, World!
// URL-safe Base64 (no padding)
urlEncoded := base64.RawURLEncoding.EncodeToString(input)
fmt.Println(urlEncoded) // SGVsbG8sIFdvcmxkIQ
}use base64::{engine::general_purpose, Engine as _};
fn main() {
let input = b"Hello, World!";
// Standard Base64
let encoded = general_purpose::STANDARD.encode(input);
println!("{}", encoded); // SGVsbG8sIFdvcmxkIQ==
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
println!("{}", String::from_utf8(decoded).unwrap());
// URL-safe Base64 (no padding)
let url_encoded = general_purpose::URL_SAFE_NO_PAD.encode(input);
println!("{}", url_encoded); // SGVsbG8sIFdvcmxkIQ
}# Encode (Linux)
echo -n "Hello, World!" | base64
# SGVsbG8sIFdvcmxkIQ==
# Decode
echo "SGVsbG8sIFdvcmxkIQ==" | base64 --decode
# Hello, World!
# Encode a file
base64 image.png > image.b64
# macOS uses -b 0 instead of --decode / -D
echo "SGVsbG8sIFdvcmxkIQ==" | base64 -D # macOS
# URL-safe Base64 with tr
echo -n "Hello" | base64 | tr '+/' '-_' | tr -d '='import java.util.Base64;
import java.nio.charset.StandardCharsets;
byte[] input = "Hello, World!".getBytes(StandardCharsets.UTF_8);
// Standard Base64
String encoded = Base64.getEncoder().encodeToString(input);
// SGVsbG8sIFdvcmxkIQ==
byte[] decoded = Base64.getDecoder().decode(encoded);
String back = new String(decoded, StandardCharsets.UTF_8);
// URL-safe Base64 (no padding)
String urlEncoded = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(input);Edge Cases and Common Mistakes
1. btoa() breaks on non-Latin-1 characters
The browser-native btoa() function only accepts Latin-1 characters (byte range 0x00–0xFF). Passing a string with emoji or CJK characters throws InvalidCharacterError. Use the UTF-8 encode pattern shown in the JavaScript example, or TextEncoder/TextDecoder with the WebCrypto API.
2. Padding mismatch
Some systems strip the trailing = padding; others require it. If you receive a "malformed base64" error, try adding = signs until the length is a multiple of 4:
const padded = token + '='.repeat((4 - token.length % 4) % 4);
3. Line wrapping (MIME)
RFC 2045 (MIME) specifies that Base64-encoded email body parts must have line breaks every 76 characters. Standard Base64 implementations (used for JWTs, APIs, etc.) do not add line breaks. If you copy a Base64 string from an email client, strip all whitespace before decoding.
4. Base64 is not encryption
Base64 is trivially reversible by anyone. Never use it as a security mechanism. HTTP Basic Auth, for example, sends credentials in plain Base64 — it only works securely over HTTPS. If you need to protect data, use proper encryption (AES-GCM, ChaCha20-Poly1305) not Base64.