TL;DR
Base64 converts 3 bytes into 4 ASCII characters using a 64-character alphabet (A-Z, a-z, 0-9, +/). Use btoa()/atob() in the browser, Buffer.from() in Node.js, and the built-in base64 module in Python. Base64URL swaps + for - and / for _ for URL safety. Base64 is encoding, NOT encryption β anyone can decode it.
What Is Base64?
Base64 is a binary-to-text encoding scheme defined in RFC 4648. It represents arbitrary binary data using only 64 printable ASCII characters, making it safe to transmit through text-based protocols like HTTP, SMTP, and JSON. The name comes directly from the 64-character alphabet it uses.
The algorithm takes 3 bytes (24 bits) of input at a time and splits them into 4 groups of 6 bits. Each 6-bit group (values 0β63) maps to one character in the Base64 alphabet. Since 6 bits can represent 64 distinct values, this perfectly covers the full alphabet.
How 3 Bytes Become 4 Characters
Example: encoding the string "Man":
M= 77 =01001101a= 97 =01100001n= 110 =01101110
Concatenated: 010011010110000101101110 β split into 6-bit groups: 010011 (19=T), 010110 (22=W), 000101 (5=F), 101110 (46=u) β result: TWFu
The Base64 Alphabet
The standard alphabet: AβZ (0β25), aβz (26β51), 0β9 (52β61), + (62), / (63). The = character is used as padding when the input is not a multiple of 3 bytes.
- 1 remaining byte β 2 Base64 chars +
== - 2 remaining bytes β 3 Base64 chars +
= - 3 bytes exactly β 4 Base64 chars, no padding
Base64 vs Base64URL vs Base32 vs Hex
| Format | Alphabet | URL Safe | Padding | Size Increase | Primary Use Case |
|---|---|---|---|---|---|
| Base64 | A-Z, a-z, 0-9, +/ | No | = | ~33% | Email attachments, data URIs, API payloads |
| Base64URL | A-Z, a-z, 0-9, -_ | Yes | Optional | ~33% | JWT tokens, URL params, filenames |
| Base32 | A-Z, 2-7 | Yes | = | ~60% | TOTP secrets, case-insensitive systems |
| Hex | 0-9, a-f | Yes | None | ~100% | Hashes, colors, binary inspection |
JavaScript β Browser (btoa / atob)
Modern browsers provide two global functions for Base64 encoding and decoding:
btoa(string)β encodes a binary string to Base64 ("binary to ASCII")atob(base64)β decodes a Base64 string back to binary ("ASCII to binary")
Basic ASCII Encoding
// Encode
const encoded = btoa('Hello, World!');
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Decode
const decoded = atob('SGVsbG8sIFdvcmxkIQ==');
console.log(decoded); // "Hello, World!"
// Encode binary string (e.g., from fetch response)
const response = await fetch('/api/data');
const arrayBuffer = await response.arrayBuffer();
const bytes = new Uint8Array(arrayBuffer);
const binaryString = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
const b64 = btoa(binaryString);Why btoa() Fails on Unicode
btoa() was designed for binary strings where each character has a code point between 0 and 255. Passing characters outside this range (emoji, Chinese characters, etc.) throws a InvalidCharacterError:
// This throws: "InvalidCharacterError: Failed to execute 'btoa'"
btoa('Hello π'); // ERROR!
btoa('δΈζ'); // ERROR!Fix: Encoding Unicode with encodeURIComponent
// Encode Unicode to Base64
function encodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
String.fromCharCode('0x' + p1)
));
}
// Decode Base64 to Unicode
function decodeUnicode(b64) {
return decodeURIComponent(
atob(b64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
}
const encoded = encodeUnicode('Hello π');
console.log(encoded); // "SGVsbG8g8J+MjQ=="
console.log(decodeUnicode(encoded)); // "Hello π"Modern Approach: TextEncoder for Binary Data
// Encode any string using TextEncoder (handles all Unicode)
function toBase64(str) {
const bytes = new TextEncoder().encode(str);
const binString = String.fromCodePoint(...bytes);
return btoa(binString);
}
// Decode Base64 back to string using TextDecoder
function fromBase64(b64) {
const binString = atob(b64);
const bytes = Uint8Array.from(binString, m => m.codePointAt(0));
return new TextDecoder().decode(bytes);
}
console.log(toBase64('Hello π')); // Properly encoded
console.log(fromBase64(toBase64('Hello π'))); // "Hello π"JavaScript β Node.js (Buffer)
Node.js uses the Buffer class for Base64 operations. This works for both text and binary data without any Unicode workarounds.
String Encoding and Decoding
// Encode string to Base64
const encoded = Buffer.from('Hello, World!').toString('base64');
console.log(encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Decode Base64 to string
const decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64').toString('utf8');
console.log(decoded); // "Hello, World!"
// Unicode strings work natively
const unicodeEncoded = Buffer.from('Hello π').toString('base64');
const unicodeDecoded = Buffer.from(unicodeEncoded, 'base64').toString('utf8');
console.log(unicodeDecoded); // "Hello π"URL-Safe Base64 (base64url)
// Node.js 16+ natively supports 'base64url'
const urlSafe = Buffer.from('Hello+World/Test==').toString('base64url');
console.log(urlSafe); // No +, /, or = characters
// Decode base64url
const original = Buffer.from(urlSafe, 'base64url').toString('utf8');
// Manual conversion for older Node.js versions
function toBase64url(b64) {
return b64.replace(/+/g, '-').replace(///g, '_').replace(/=/g, '');
}
function fromBase64url(b64url) {
const pad = b64url.length % 4;
const padded = b64url + (pad ? '='.repeat(4 - pad) : '');
return padded.replace(/-/g, '+').replace(/_/g, '/');
}File Encoding with fs + Buffer
const fs = require('fs');
// Encode file to Base64
const fileBuffer = fs.readFileSync('./image.png');
const base64String = fileBuffer.toString('base64');
console.log(`data:image/png;base64,${base64String}`);
// Decode Base64 back to file
const base64Data = 'iVBORw0KGgo...'; // your base64 string
const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync('./decoded-image.png', buffer);
console.log('File saved successfully');Base64URL β URL-Safe Encoding
Standard Base64 uses + and / which have special meanings in URLs and filenames. Base64URL (defined in RFC 4648 Section 5) solves this:
+is replaced with-(hyphen)/is replaced with_(underscore)- Padding
=is usually omitted
When to Use Base64URL
| Use Case | Why Base64URL |
|---|---|
| JWT tokens | Token appears in Authorization headers and URLs without escaping |
| URL query parameters | + would be interpreted as a space in some parsers |
| Filenames | / would create directory separators on Unix |
| Email-safe tokens | Avoids characters that email clients might treat specially |
Conversion Code
// Standard Base64 β Base64URL
function base64ToBase64url(b64) {
return b64.replace(/+/g, '-').replace(///g, '_').replace(/=+$/, '');
}
// Base64URL β Standard Base64
function base64urlToBase64(b64url) {
const padLen = (4 - (b64url.length % 4)) % 4;
return b64url.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padLen);
}
// Examples
const standard = 'SGVsbG8+V29ybGQv';
const urlSafe = base64ToBase64url(standard); // "SGVsbG8-V29ybGQv"
const back = base64urlToBase64(urlSafe); // "SGVsbG8+V29ybGQv"Python β base64 Module
Python includes a built-in base64 module that handles standard Base64, URL-safe Base64, Base32, and more. All functions work with bytes objects.
Standard Encoding and Decoding
import base64
# Encode bytes to Base64
data = b"Hello, World!"
encoded = base64.b64encode(data)
print(encoded) # b'SGVsbG8sIFdvcmxkIQ=='
print(encoded.decode('utf-8')) # String: "SGVsbG8sIFdvcmxkIQ=="
# Decode Base64 to bytes
decoded = base64.b64decode(b'SGVsbG8sIFdvcmxkIQ==')
print(decoded) # b'Hello, World!'
print(decoded.decode('utf-8')) # String: "Hello, World!"
# Encode a string (convert to bytes first)
text = "Hello π"
encoded = base64.b64encode(text.encode('utf-8'))
decoded_text = base64.b64decode(encoded).decode('utf-8')
print(decoded_text) # "Hello π"URL-Safe Base64
import base64
# URL-safe encoding (replaces + with - and / with _)
data = b"Hello+World/Test"
url_safe = base64.urlsafe_b64encode(data)
print(url_safe) # b'SGVsbG8rV29ybGQvVGVzdA=='
# URL-safe decoding
decoded = base64.urlsafe_b64decode(url_safe)
print(decoded) # b'Hello+World/Test'
# Remove padding for JWT-style encoding
no_pad = url_safe.rstrip(b'=').decode('utf-8')
print(no_pad) # "SGVsbG8rV29ybGQvVGVzdA"
# Add padding back before decoding
def decode_base64url(b64url):
padded = b64url + '=' * (4 - len(b64url) % 4)
return base64.urlsafe_b64decode(padded)Encoding Files in Python
import base64
# Encode a file to Base64
with open('document.pdf', 'rb') as f:
file_data = f.read()
encoded = base64.b64encode(file_data).decode('utf-8')
print(f"Encoded length: {len(encoded)} chars")
# Decode and save Base64 back to file
with open('output.pdf', 'wb') as f:
f.write(base64.b64decode(encoded))
# Encode image for data URI
with open('logo.png', 'rb') as img:
img_data = base64.b64encode(img.read()).decode('utf-8')
data_uri = f"data:image/png;base64,{img_data}"
print(data_uri[:100] + '...')Image to Base64 β Browser File API
Embedding images as Base64 data URIs allows them to be used inline in HTML, CSS, and JavaScript without separate HTTP requests. This is useful for small icons, email templates, and offline-capable apps.
Using FileReader to Convert an Uploaded Image
// HTML: <input type="file" id="imageInput" accept="image/*">
// HTML: <img id="preview" src="" alt="Preview">
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
// event.target.result is "data:image/png;base64,iVBORw0..."
const dataURL = event.target.result;
// Display the image
document.getElementById('preview').src = dataURL;
// Extract just the Base64 string (without the data URI prefix)
const base64String = dataURL.split(',')[1];
console.log('Base64 length:', base64String.length);
console.log('MIME type:', file.type);
// Send to server
fetch('/api/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image: base64String, mimeType: file.type }),
});
};
reader.readAsDataURL(file); // This triggers encoding
});Using Canvas for Image Transformation
// Convert image URL to Base64 using Canvas
function imageUrlToBase64(url) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = 'anonymous'; // Required for cross-origin images
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve(canvas.toDataURL('image/png')); // data:image/png;base64,...
};
img.onerror = reject;
img.src = url;
});
}
// Usage
const dataUrl = await imageUrlToBase64('https://example.com/logo.png');
document.querySelector('img').src = dataUrl;Using Inline Base64 Images in HTML and CSS
<!-- Inline in HTML img tag -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEA..." alt="1px dot">
<!-- Inline in CSS background -->
<style>
.icon {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL...");
width: 24px;
height: 24px;
}
</style>
<!-- SVG can also be used directly without base64 -->
<img src="data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg'><circle cx='5' cy='5' r='5'/></svg>" alt="circle">When to Use vs When NOT to Use Image Base64
| Scenario | Use Base64? | Reason |
|---|---|---|
| Small icons (<5KB) | Yes | Reduces HTTP requests, negligible size penalty |
| Email template images | Yes | External images often blocked by email clients |
| Offline PWA assets | Yes | Avoids network dependency for critical assets |
| Large photos (>10KB) | No | 33% larger, no browser caching, slow initial load |
| Page hero images | No | Blocks HTML parsing, cannot be cached separately |
| CDN-served images | No | CDN caching provides better performance |
Go β encoding/base64
Go's standard library includes the encoding/base64 package with support for standard Base64, URL-safe Base64, and raw (no padding) variants.
package main
import (
"encoding/base64"
"fmt"
)
func main() {
input := []byte("Hello, World!")
// Standard Base64 (with padding)
encoded := base64.StdEncoding.EncodeToString(input)
fmt.Println(encoded) // "SGVsbG8sIFdvcmxkIQ=="
// Decode
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
panic(err)
}
fmt.Println(string(decoded)) // "Hello, World!"
// URL-safe Base64 (with padding)
urlEncoded := base64.URLEncoding.EncodeToString(input)
fmt.Println(urlEncoded)
// URL-safe Base64 WITHOUT padding (for JWT)
rawURLEncoded := base64.RawURLEncoding.EncodeToString(input)
fmt.Println(rawURLEncoded) // No = at the end
// Decode raw URL-safe
rawDecoded, _ := base64.RawURLEncoding.DecodeString(rawURLEncoded)
fmt.Println(string(rawDecoded))
// Standard Base64 WITHOUT padding
rawStdEncoded := base64.RawStdEncoding.EncodeToString(input)
fmt.Println(rawStdEncoded)
}Go Base64 Encoding Variants Summary
| Go Constant | Alphabet | Padding | Use Case |
|---|---|---|---|
base64.StdEncoding | A-Z, a-z, 0-9, +/ | Yes (=) | Standard MIME / file encoding |
base64.URLEncoding | A-Z, a-z, 0-9, -_ | Yes (=) | URL parameters |
base64.RawURLEncoding | A-Z, a-z, 0-9, -_ | No | JWT tokens, compact URLs |
base64.RawStdEncoding | A-Z, a-z, 0-9, +/ | No | Compact data transfer |
Rust β base64 Crate
Rust uses the base64 crate (not in std library). Add it to your Cargo.toml: base64 = "0.22"
use base64::{Engine as _, engine::general_purpose};
fn main() {
let input = b"Hello, World!";
// Standard Base64 encode
let encoded = general_purpose::STANDARD.encode(input);
println!("{}", encoded); // "SGVsbG8sIFdvcmxkIQ=="
// Standard Base64 decode
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
println!("{}", String::from_utf8(decoded).unwrap()); // "Hello, World!"
// URL-safe Base64 with padding
let url_encoded = general_purpose::URL_SAFE.encode(input);
println!("{}", url_encoded);
// URL-safe WITHOUT padding (for JWT)
let jwt_encoded = general_purpose::URL_SAFE_NO_PAD.encode(input);
println!("{}", jwt_encoded); // No = padding
// Decode URL-safe without padding
let jwt_decoded = general_purpose::URL_SAFE_NO_PAD.decode(&jwt_encoded).unwrap();
println!("{}", String::from_utf8(jwt_decoded).unwrap());
}
// Custom engine example
use base64::engine::general_purpose::GeneralPurpose;
use base64::alphabet;
let custom_engine = GeneralPurpose::new(
&alphabet::URL_SAFE,
base64::engine::general_purpose::NO_PAD,
);
let encoded = custom_engine.encode(b"test data");Base64 in HTTP β Authentication, Data URIs, MIME
HTTP Basic Authentication
HTTP Basic Auth encodes credentials as Base64 in the Authorization header. Note: this is not encryption β the credentials are trivially decodable. Always use HTTPS.
// Format: "username:password" β Base64
const username = 'admin';
const password = 'secret123';
const credentials = btoa(`${username}:${password}`);
// credentials = "YWRtaW46c2VjcmV0MTIz"
// HTTP request with Basic Auth
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
});
// Server-side decoding (Node.js)
const authHeader = req.headers.authorization; // "Basic YWRtaW46c2VjcmV0MTIz"
const base64Credentials = authHeader.split(' ')[1];
const [user, pass] = Buffer.from(base64Credentials, 'base64').toString('utf8').split(':');
console.log(user, pass); // "admin" "secret123"CSS Data URIs
/* Inline SVG as Base64 in CSS */
.icon-home {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwIDIwdi02aDR2Nmg1di04aDNMMTIgM0wyIDEyaDN2OHoiLz48L3N2Zz4=");
background-size: contain;
width: 24px;
height: 24px;
}
/* Inline PNG as Base64 */
.logo {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...");
}
/* Font as Base64 for offline use */
@font-face {
font-family: 'CustomFont';
src: url("data:font/woff2;base64,d09GMgABAAA...") format('woff2');
}MIME Email Attachments
// Email attachment using Node.js (nodemailer)
const nodemailer = require('nodemailer');
const fs = require('fs');
const pdfBuffer = fs.readFileSync('report.pdf');
const pdfBase64 = pdfBuffer.toString('base64');
const mailOptions = {
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Monthly Report',
text: 'Please find the report attached.',
attachments: [
{
filename: 'report.pdf',
content: pdfBase64,
encoding: 'base64',
contentType: 'application/pdf',
},
],
};
// Raw MIME format (Content-Transfer-Encoding: base64)
/*
Content-Type: application/pdf; name="report.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0xLjQKJeLjz9MKNSAwIG9iago8PAovRmlsdGVyIC9GbGF0ZURlY29kZQov...
*/Streaming and Large File Encoding
Loading large files entirely into memory before encoding is inefficient and may crash for very large files. Use streaming approaches instead.
Critical rule: Chunk sizes for streaming Base64 encoding must be multiples of 3 bytes. Each 3-byte group maps to exactly 4 Base64 characters. Using non-multiples produces incorrect padding characters in intermediate chunks.
Node.js Streams
const fs = require('fs');
const { Transform } = require('stream');
// Custom Base64 encoding stream
class Base64Encoder extends Transform {
constructor() {
super();
this._buffer = Buffer.alloc(0);
}
_transform(chunk, encoding, callback) {
// Combine with leftover bytes
this._buffer = Buffer.concat([this._buffer, chunk]);
// Process in multiples of 3
const safeLength = Math.floor(this._buffer.length / 3) * 3;
if (safeLength > 0) {
this.push(this._buffer.slice(0, safeLength).toString('base64'));
this._buffer = this._buffer.slice(safeLength);
}
callback();
}
_flush(callback) {
if (this._buffer.length > 0) {
this.push(this._buffer.toString('base64'));
}
callback();
}
}
// Usage: stream file through encoder
const readStream = fs.createReadStream('large-video.mp4');
const encoder = new Base64Encoder();
const writeStream = fs.createWriteStream('large-video.b64');
readStream.pipe(encoder).pipe(writeStream);
writeStream.on('finish', () => console.log('Streaming encode complete'));Python Chunked Encoding
import base64
def encode_file_streaming(input_path, output_path, chunk_size=57):
"""
Encode a large file using chunked streaming.
chunk_size must be a multiple of 3 (57 = 3 * 19 β produces 76 char lines, MIME standard).
"""
with open(input_path, 'rb') as infile, open(output_path, 'w') as outfile:
while True:
chunk = infile.read(chunk_size)
if not chunk:
break
# Each 57-byte chunk produces 76 Base64 characters
encoded_chunk = base64.b64encode(chunk).decode('utf-8')
outfile.write(encoded_chunk + '\n') # MIME adds newlines
# base64.encodebytes() automatically adds newlines every 76 chars
with open('large-file.bin', 'rb') as f:
encoded = base64.encodebytes(f.read())
print(encoded[:100]) # b'SGVsbG8...'
# Streaming decode
def decode_file_streaming(input_path, output_path):
with open(input_path, 'r') as infile, open(output_path, 'wb') as outfile:
for line in infile:
chunk = base64.b64decode(line.strip())
outfile.write(chunk)Performance Comparison
| Format | Output Size Increase | Encoding Speed | URL Safe | Best Use Case |
|---|---|---|---|---|
| Hex | +100% (2x size) | Very Fast | Yes | Hash values, debugging binary data |
| Base64 | +33% | Fast | No | Email attachments, data URIs, API payloads |
| Base64URL | +33% | Fast | Yes | JWT tokens, URL params, filenames |
| Base32 | +60% | Moderate | Yes | TOTP secrets, human-readable codes |
Base64 vs raw binary: If your protocol supports binary data (e.g., WebSockets, HTTP/2, gRPC), always prefer raw binary over Base64 encoded text. You avoid the 33% overhead and the CPU cost of encoding/decoding.
Browser performance: Native btoa()/atob() are implemented in C++ and are very fast. For data under 1MB, they are typically instantaneous. For larger data, consider a Web Worker to avoid blocking the main thread.
Common Mistakes and How to Fix Them
1. Using btoa() on Unicode Strings
// WRONG β throws InvalidCharacterError
const encoded = btoa("Hello π"); // Error!
// CORRECT β encode URI components first
const encoded = btoa(encodeURIComponent("Hello π")
.replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode('0x' + p1)));
// OR use TextEncoder (modern approach)
const bytes = new TextEncoder().encode("Hello π");
const encoded = btoa(String.fromCodePoint(...bytes));2. Forgetting Padding When Decoding
// Some APIs return Base64 without padding
const b64NoPad = "SGVsbG8"; // Missing ==
// WRONG
atob(b64NoPad); // May throw or return incorrect data
// CORRECT β add padding before decoding
function safeDecode(b64) {
const padded = b64 + '='.repeat((4 - b64.length % 4) % 4);
return atob(padded);
}
console.log(safeDecode("SGVsbG8")); // "Hello"3. Treating Base64 as Encryption
// WRONG β Base64 is NOT encryption
const "secret" = btoa("password123"); // Anyone can decode this!
// btoa("password123") = "cGFzc3dvcmQxMjM=" β trivially reversible
// CORRECT β use actual encryption
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
key,
new TextEncoder().encode("password123")
);
// Now encode the encrypted bytes as Base64 for storage/transport
const encryptedB64 = btoa(String.fromCharCode(...new Uint8Array(encrypted)));4. Confusing Base64URL with Standard Base64
// JWT tokens use Base64URL (- and _ instead of + and /)
const jwtPayload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
// WRONG β trying to decode JWT part with standard atob()
atob(jwtPayload); // May fail if it contains - or _
// CORRECT β convert Base64URL to standard Base64 first
function decodeBase64url(b64url) {
const padded = b64url.replace(/-/g, '+').replace(/_/g, '/')
+ '='.repeat((4 - b64url.length % 4) % 4);
return JSON.parse(atob(padded));
}
const payload = decodeBase64url(jwtPayload);
console.log(payload); // { alg: 'HS256', typ: 'JWT' }5. MIME Base64 Line Breaks
// MIME/email Base64 adds \r\n every 76 characters
// Some implementations choke on these line breaks
// Python's base64.encodebytes() adds newlines automatically
import base64
encoded_with_newlines = base64.encodebytes(b"Hello World " * 100)
# b'SGVsbG8gV29ybGQgSGVsbG8gV29ybGQg...'
# Note: each 76-char line ends with \n
# To get clean Base64 without newlines, use b64encode
clean = base64.b64encode(b"Hello World " * 100)
# Node.js does NOT add line breaks β this is safe
Buffer.from(data).toString('base64') // No line breaks
// If you receive MIME Base64, strip whitespace before decoding
const cleanB64 = mimeB64.replace(/[\r\n\s]/g, '');
const decoded = atob(cleanB64);6. Using Non-Multiple-of-3 Chunk Sizes for Streaming
// WRONG β chunk size 1000 is not a multiple of 3
// This produces incorrect = padding after each chunk
const chunks = [];
for (let i = 0; i < data.length; i += 1000) {
chunks.push(btoa(String.fromCharCode(...data.slice(i, i + 1000))));
}
// Concatenating these produces WRONG output!
// CORRECT β use multiples of 3: 3, 6, 57, 3072, etc.
const CHUNK_SIZE = 3072; // 3 * 1024
const chunks = [];
for (let i = 0; i < data.length; i += CHUNK_SIZE) {
chunks.push(btoa(String.fromCharCode(...data.slice(i, i + CHUNK_SIZE))));
}
// Only the LAST chunk may have = padding β this is correct!Key Takeaways
- Base64 converts 3 bytes β 4 characters using a 64-char alphabet; output is always ~33% larger than input.
- Browser: use
btoa()/atob()for ASCII,TextEncoder+TextDecoderfor Unicode strings. - Node.js:
Buffer.from(str).toString('base64')andBuffer.from(b64, 'base64').toString('utf8')handle all cases. - Python:
base64.b64encode()andbase64.b64decode()work withbytesobjects; encode strings with.encode('utf-8')first. - Go: use
base64.StdEncoding,base64.URLEncoding, orbase64.RawURLEncodingfromencoding/base64. - Rust: use the
base64crate withgeneral_purpose::STANDARDorURL_SAFE_NO_PADengines. - Base64URL replaces
+β-and/β_for JWT tokens and URL parameters. - For streaming/large files, always use chunk sizes that are multiples of 3 bytes.
- Base64 is encoding, NOT encryption β never use it to protect sensitive data.
- Inline Base64 images are only worthwhile for assets under ~5KB; larger images lose browser caching benefits.