正在加载,请稍候…

URL Encoding vs Base64 vs Hex: When to Use Which

A practical comparison of URL encoding (percent encoding), Base64, and hexadecimal encoding — what each one does, when to use each, and common mistakes that cause data corruption.

Three Encodings, Three Different Jobs

URL encoding, Base64, and hex are all ways to represent data as printable ASCII text — but they solve completely different problems. Using the wrong one is a common source of bugs:

  • Passing a Base64 string directly in a URL breaks when it contains + and /
  • URL-encoding binary data instead of Base64ing it produces strings 3× the original size
  • Storing a password hash as Base64 instead of hex is fine, but mixing them in a system causes comparison failures

Here's when to reach for each one.

URL Encoding (Percent Encoding)

Purpose: Make arbitrary text safe to include in a URL.

How it works: Every character that isn't a URL-safe alphanumeric or one of - _ . ~ gets replaced with % followed by its two-digit hex code.

Space    → %20
&        → %26
=        → %3D
/        → %2F
+        → %2B
"hello world" → "hello%20world"

When to use it:

  • Query string parameters: ?name=John+Doe or ?name=John%20Doe
  • Path segments containing special characters
  • Form data submission (application/x-www-form-urlencoded)

When NOT to use it:

  • Binary data (images, files) — the output is enormous
  • Data that will be stored in a database — decode before storing
  • Passwords or secrets — they should never appear in URLs at all
// JavaScript
encodeURIComponent('hello world & more')  // "hello%20world%20%26%20more"
decodeURIComponent('hello%20world')       // "hello world"

// encodeURI vs encodeURIComponent
encodeURI('https://example.com/path?q=hello world')
// "https://example.com/path?q=hello%20world"  ← leaves : // ? = & intact

encodeURIComponent('hello world')
// "hello%20world"  ← encodes everything including : / ? = &
from urllib.parse import quote, unquote, urlencode

quote('hello world & more')     # 'hello%20world%20%26%20more'
quote('hello/world', safe='/')  # 'hello/world' — / not encoded
unquote('hello%20world')        # 'hello world'

# For query strings
urlencode({'name': 'John Doe', 'age': 30})
# 'name=John+Doe&age=30'  ← spaces as + in query strings

Base64

Purpose: Represent binary data as printable ASCII text.

How it works: Takes 3 bytes of binary data and encodes them as 4 ASCII characters from a 64-character alphabet (A-Z a-z 0-9 + /). Output size is always 4/3 (33%) larger than the input.

"Hello" → "SGVsbG8="

When to use it:

  • Embedding binary files (images, PDFs) in JSON or XML
  • Email attachments (MIME encoding)
  • HTTP Basic Authentication headers (Authorization: Basic dXNlcjpwYXNz)
  • Storing small binary blobs in text fields
  • Data URLs (<img src="data:image/png;base64,iVBOR...">)

When NOT to use it:

  • URLs — the + and / characters are valid Base64 but have special meaning in URLs. Use Base64url instead (replaces + with - and / with _, removes padding)
  • Large files — the 33% size overhead adds up
  • Data that needs to be human-readable
// Browser
btoa('Hello World')             // "SGVsbG8gV29ybGQ="
atob('SGVsbG8gV29ybGQ=')       // "Hello World"

// Node.js
Buffer.from('Hello World').toString('base64')         // "SGVsbG8gV29ybGQ="
Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString() // "Hello World"

// Base64url (URL-safe, no padding)
Buffer.from('Hello World').toString('base64url')      // "SGVsbG8gV29ybGQ"
import base64

base64.b64encode(b'Hello World')          # b'SGVsbG8gV29ybGQ='
base64.b64decode('SGVsbG8gV29ybGQ=')     # b'Hello World'

# URL-safe Base64
base64.urlsafe_b64encode(b'Hello World') # b'SGVsbG8gV29ybGQ='

Hexadecimal (Hex) Encoding

Purpose: Represent binary data as a human-readable string of hex digits.

How it works: Each byte becomes exactly two hex digits (0–9, a–f). A 32-byte SHA-256 hash becomes a 64-character hex string.

0x48 0x65 0x6c 0x6c 0x6f → "48656c6c6f"

When to use it:

  • Cryptographic hashes (SHA-256, MD5): a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
  • Binary identifiers that need to be readable: transaction IDs, fingerprints
  • Debugging binary data
  • Color codes: #FF5733
  • MAC addresses: 00:1A:2B:3C:4D:5E

When NOT to use it:

  • When space efficiency matters — hex is 2× the size of the original binary (compared to Base64's 1.33×)
  • Embedding binary in JSON or HTTP — use Base64 instead
// Node.js
crypto.createHash('sha256').update('hello').digest('hex')
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

Buffer.from('Hello').toString('hex')  // "48656c6c6f"
Buffer.from('48656c6c6f', 'hex').toString()  // "Hello"
import hashlib

hashlib.sha256(b'hello').hexdigest()
# "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

b'Hello'.hex()                          # '48656c6c6f'
bytes.fromhex('48656c6c6f')            # b'Hello'

Side-by-Side Comparison

Property URL Encoding Base64 Hex
Input Text strings Any binary Any binary
Output size Variable (up to 3×) 4/3 × input 2× input
URL-safe Yes (that's the point) No (use Base64url) Yes
Human-readable Somewhat No Somewhat
Standard use Query params, form data Binary in text contexts Hashes, fingerprints
Padding None = padding None

The Base64 in URLs Trap

JWT tokens use Base64url — not standard Base64. If you decode a JWT's header or payload with regular Base64, it works because the specific bytes happen to not contain + or /. But if your JWT library produces standard Base64 and you put it in a URL without re-encoding, any + becomes a space and / creates a path segment.

// Safe JWT in URL (base64url, no padding)
const header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';  // no + / or =

// Dangerous: standard base64 in URL
const standard = Buffer.from(data).toString('base64');
// May contain + / = — must re-encode for URLs:
const urlSafe = standard.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

→ Encode and decode URLs with the URL Encoder or encode Base64 with the Base64 Converter.