Demystifying binary-to-text encoding. Learn how Base64 works, when to use it for data transmission, and how to decode strings securely on the client side.
Category: Security
Base64 is everywhere: in API payloads, JWTs, email attachments, CSS backgrounds, and even URL parameters. Yet it’s one of the most frequently misunderstood building blocks in web development and security.
This authoritative guide explains Base64 from first principles, shows how and why it’s used, and—critically—what it does not do. You’ll also get production-ready code, validation patterns, and security checklists so you can ship safely and efficiently.
- and _, often no padding).Base64 is a binary‑to‑text encoding that converts every 3 bytes (24 bits) into 4 ASCII characters using a 64‑symbol alphabet. It’s designed to move binary data across systems that expect text. Base64 is not encryption, offers no confidentiality, and is easily decoded by anyone.
Many early protocols and systems were designed for 7‑bit text and could corrupt or reinterpret raw bytes above the ASCII range (for example, control characters or high‑bit values). Base64 confines output to a safe, portable subset of printable characters so binary data can survive transit through email, logs, form fields, JSON, XML, and other text‑centric layers.
What Base64 is not:
Use Base64 when:
Avoid Base64 when:
A–Z a–z 0–9 + /= padding to indicate missing bytes.Padding rules:
===Example: "hello" → aGVsbG8=
Walkthrough for "hel":
h (0x68), e (0x65), l (0x6C)a G V s → aGVsLine breaks:
A–Z a–z 0–9 + /, uses = padding. Suitable for general binary‑to‑text.+ with - and / with _, so it’s URL and filename safe. Padding is optional; many libraries omit it for compactness.Why it matters:
= will be present.Security warnings:
ceil(n_bytes / 3) * 4 characters output (excluding potential line breaks).Example size math:
\n, \\, etc., if present.Performance tips:
exp, nbf, iat, iss, aud, and kid.=.Content-Transfer-Encoding: base64 and Content-Type with correct charset and boundary.Authorization: Basic <base64(username:password)>.img-src data:. Treat SVG data URIs as active content—sanitize to prevent script execution in some contexts.Rules of thumb:
= only as padding at the end.- and _ instead of + and /.Robust decoders should:
Regex patterns (validation helpers):
^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}|[A-Za-z0-9_-]{3})?$Note: Regex helps for quick checks, but a decoder with strict mode is more authoritative.
Use these minimal, production‑ready snippets. Each example includes Base64URL helpers and common pitfalls.
btoa/atob are ASCII‑only. Use TextEncoder/TextDecoder for UTF‑8 safety.
// UTF-8 safe Base64 in modern browsers
function encodeBase64Utf8(str) {
const bytes = new TextEncoder().encode(str);
let bin = '';
bytes.forEach(b => bin += String.fromCharCode(b));
return btoa(bin); // standard Base64 with + and /
}
function decodeBase64Utf8(b64) {
const bin = atob(b64.replace(/\s+/g, '')); // strip whitespace just in case
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
// Base64URL helpers
function toBase64Url(b64) {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function fromBase64Url(b64url) {
let b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
const pad = b64.length % 4;
if (pad) b64 += '='.repeat(4 - pad);
return b64;
}
console.log(encodeBase64Utf8('こんにちは'));
// Standard Base64
const b64 = Buffer.from('hello 🌍', 'utf8').toString('base64');
const raw = Buffer.from(b64, 'base64').toString('utf8');
// Base64URL helpers
function toBase64Url(b64) {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function fromBase64Url(b64url) {
let b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
const pad = b64.length % 4;
if (pad) b64 += '='.repeat(4 - pad);
return b64;
}
// Streaming example (avoid buffering large files)
import { createReadStream, createWriteStream } from 'node:fs';
import { Transform, pipeline } from 'node:stream';
const base64Encoder = new Transform({
transform(chunk, _enc, cb) {
// chunk -> Base64; ensure chunk boundaries don’t split 3-byte groups
// Simpler: use built-in stream pipeline in Node 20+ with web streams
cb(null, Buffer.from(chunk).toString('base64'));
}
});
// For production, prefer libraries that handle proper chunk alignment and CRLF if needed.
import base64
# Standard Base64
raw = 'hello 🌍'.encode('utf-8')
b64 = base64.b64encode(raw).decode('ascii')
raw_again = base64.b64decode(b64).decode('utf-8')
# Base64URL (no padding on output)
b64url = base64.urlsafe_b64encode(raw).decode('ascii').rstrip('=')
# To decode, restore padding
pad = '=' * (-len(b64url) % 4)
raw_again_url = base64.urlsafe_b64decode((b64url + pad).encode('ascii')).decode('utf-8')
# Streaming files without loading all into memory
with open('input.bin', 'rb') as fin, open('output.b64', 'wb') as fout:
base64.encode(fin, fout) # writes with newlines by default
with open('output.b64', 'rb') as fin, open('decoded.bin', 'wb') as fout:
base64.decode(fin, fout)
package main
import (
"encoding/base64"
"fmt"
)
func main() {
data := []byte("hello 🌍")
// Standard Base64
b64 := base64.StdEncoding.EncodeToString(data)
dec, err := base64.StdEncoding.DecodeString(b64)
if err != nil { panic(err) }
fmt.Println(string(dec))
// Base64URL without padding
b64url := base64.RawURLEncoding.EncodeToString(data)
dec2, err := base64.RawURLEncoding.DecodeString(b64url)
if err != nil { panic(err) }
fmt.Println(string(dec2))
}
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64Demo {
public static void main(String[] args) {
byte[] data = "hello 🌍".getBytes(StandardCharsets.UTF_8);
// Standard Base64
String b64 = Base64.getEncoder().encodeToString(data);
byte[] dec = Base64.getDecoder().decode(b64);
// Base64URL without padding
String b64url = Base64.getUrlEncoder().withoutPadding().encodeToString(data);
byte[] dec2 = Base64.getUrlDecoder().decode(b64url);
System.out.println(new String(dec, StandardCharsets.UTF_8));
System.out.println(new String(dec2, StandardCharsets.UTF_8));
}
}
using System;
using System.Text;
class Program {
static void Main() {
var data = Encoding.UTF8.GetBytes("hello 🌍");
var b64 = Convert.ToBase64String(data);
var dec = Convert.FromBase64String(b64);
Console.WriteLine(Encoding.UTF8.GetString(dec));
// Base64URL helpers
string ToBase64Url(string base64) => base64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
string FromBase64Url(string base64Url) {
string b = base64Url.Replace('-', '+').Replace('_', '/');
return b.PadRight(b.Length + ((4 - b.Length % 4) % 4), '=');
}
string b64url = ToBase64Url(b64);
string b64again = FromBase64Url(b64url);
Console.WriteLine(Encoding.UTF8.GetString(Convert.FromBase64String(b64again)));
}
}
use base64::{engine::general_purpose, Engine as _};
fn main() {
let data = "hello 🌍".as_bytes();
// Standard Base64
let b64 = general_purpose::STANDARD.encode(data);
let dec = general_purpose::STANDARD.decode(&b64).unwrap();
// Base64URL without padding
let b64url = general_purpose::URL_SAFE_NO_PAD.encode(data);
let dec2 = general_purpose::URL_SAFE_NO_PAD.decode(&b64url).unwrap();
println!("{}", String::from_utf8(dec).unwrap());
println!("{}", String::from_utf8(dec2).unwrap());
}
<?php
$raw = "hello 🌍";
$b64 = base64_encode($raw);
$raw_again = base64_decode($b64, true); // strict mode
// Base64URL helpers
function to_base64url($b64) {
return rtrim(strtr($b64, '+/', '-_'), '=');
}
function from_base64url($b64url) {
$b64 = strtr($b64url, '-_', '+/');
return str_pad($b64, strlen($b64) + (4 - strlen($b64) % 4) % 4, '=', STR_PAD_RIGHT);
}
?>
require 'base64'
data = 'hello 🌍'
b64 = Base64.strict_encode64(data)
raw = Base64.decode64(b64)
# Base64URL helpers
b64url = b64.tr('+/', '-_').delete('=')
# To decode, restore padding
pad = '=' * ((4 - b64url.length % 4) % 4)
raw2 = Base64.decode64(b64url.tr('-_', '+/') + pad)
# Linux (GNU coreutils):
echo -n 'hello 🌍' | base64 # encode
echo -n 'aGVsbG8g8J+MjA==' | base64 -d # decode
# Prevent wrapping (GNU):
echo -n 'data' | base64 -w 0
# macOS (BSD base64):
echo -n 'data' | base64 # no wrap by default
echo -n 'ZGF0YQ==' | base64 -D # decode
# OpenSSL alternative:
openssl base64 -in input.bin -out output.b64 # wrap by default
openssl base64 -A -in input.bin -out output_nowrap.b64 # no wrap
"InvalidCharacterError: The string to be decoded is not correctly encoded" (browser atob):
TextDecoder/TextEncoder, strip whitespace, ensure correct variant (Base64 vs. Base64URL)."Incorrect padding" (Python) / "Illegal base64 data at input char…" (Go):
= or wrong alphabet.Garbled Unicode on decode:
UTF-8 when the original text was UTF‑8.Extra newlines/CRLF in MIME content:
img-src data: or font-src data:. Avoid allowing data: for script-src.Before using Base64 for credentials or tokens:
iss, aud, exp, nbf, iat, and optionally jti for replay.kid only from a trusted JWKS; cache and rotate keys correctly.Threat modeling tips:
Choose Base64 when size matters more than human readability and you need a portable text form of binary.
+ → -, / → _, remove trailing = for output.=.This simple rule avoids most interoperability bugs.
Q: Why do some Base64 strings end with = or ==?
A: Padding marks that the input wasn’t a multiple of 3 bytes. == means 1 input byte, = means 2 input bytes in the final 24‑bit group.
Q: Is Base64 secure?
A: No. It’s encoding only. Anyone can decode Base64. Use encryption for secrecy.
Q: Why does my Base64 differ between systems?
A: You may be mixing Standard and Base64URL, or one system is adding line breaks (MIME). Normalize the variant and whitespace.
Q: Can I store Base64 in a database?
A: Yes, but consider binary columns (BLOB/bytea) to avoid the 33% overhead. If you must store text only, Base64 is fine with indexing considerations.
Q: Do I need to escape Base64 in JSON?
A: Not usually; Base64 uses JSON‑safe characters. Just ensure you quote it as a JSON string.
You can embed FAQ structured data to help search engines surface precise answers.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is Base64 encryption?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Base64 is a reversible binary-to-text encoding with no key."
}
},
{
"@type": "Question",
"name": "Why does Base64 use '=' padding?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Padding indicates the input length isn’t a multiple of 3 bytes and helps decoders reconstruct the missing bits."
}
},
{
"@type": "Question",
"name": "When should I use Base64URL?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Use Base64URL for tokens in URLs, cookies, and filenames to avoid reserved characters like '+' and '/'."
}
}
]
}
Math.ceil(nBytes / 3) * 4.replace(/\s+/g, '')base64(str).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')str.replace(/-/g,'+').replace(/_/g,'/').padEnd(len + ((4 - len%4)%4), '=')Written by: Senior Security Engineer & SEO Technical Writer with 10+ years auditing auth systems, email pipelines, and token services at scale. Content last reviewed: 2026-04-15.
Want a safe, client‑side Base64 decoder that never uploads your data? Try our privacy‑first tool: /tools/base64-converter
With QR codes everywhere, how do you know which one to trust? Learn about 'Quishing' attacks and how to stay safe while scanning in the digital age.
Are QR codes safe? Learn about "quishing" (QR phishing), malicious links, and the best practices for scanning and generating QR codes securely.