Skip to content
encode2026-06-265 min read

When you need to represent binary data as text, Base64 and Hex are the two default choices. Both work, but they differ significantly in density, readability, and the contexts where they shine. Choosing wrong usually isn't fatal, but it inflates storage, complicates debugging, or bloats URL length.

This guide covers how each encoding works under the hood, where each fits, and how to choose. Want to try encoding and decoding both formats yourself? Use our free Base64 Decoder to compare the output side by side.

The Mechanism

Hex (base-16): each byte is written as two hexadecimal characters (0-9, a-f). The 256 possible byte values map onto these two-character combinations.

byte 0x48 → "48"
byte 0x65 → "65"
byte 0xFF → "ff"

Base64: every 3 bytes are written as 4 Base64 characters (A-Z, a-z, 0-9, +, /). Each character carries 6 bits of data.

3 bytes = 24 bits → 4 six-bit characters

That's where the density difference comes from: Hex packs 4 bits per character, Base64 packs 6.

Density Compared

| Encoding | Chars per byte | Size overhead | Alphabet size | |----------|----------------|---------------|---------------| | Hex | 2.0 | 100% | 16 | | Base64 | ~1.33 | 33% | 64 |

For 1 KB of binary data:

  • Hex encoded: 2 KB
  • Base64 encoded: ~1.33 KB

The gap matters at scale. A 100 MB file becomes 200 MB in hex but only 133 MB in Base64.

Worked Example: Encoding "Hello"

Five bytes of "Hello", encoded both ways:

import base64

data = b"Hello"

# Hex
hex_encoded = data.hex()
# "48656c6c6f"  (10 characters)

# Base64
b64_encoded = base64.b64encode(data).decode()
# "SGVsbG8="  (8 characters, including one padding =)

print(f"raw:    {len(data)} bytes")         # 5
print(f"hex:    {len(hex_encoded)} chars")  # 10
print(f"base64: {len(b64_encoded)} chars")  # 8

Note the = padding. Base64 processes input in 3-byte groups; "Hello" is 5 bytes (one group of 3, one group of 2). The second group has only 2 bytes, so the encoded output gets 1 = to reach a multiple of 4.

Where Hex Wins

1. Readability

Every byte is independently readable, with clear byte boundaries. This is critical when debugging binary protocols:

# Hex: the third byte is visibly 0x6c
48 65 6c 6c 6f

# Base64: byte boundaries are obscured by the 6-bit grouping
SGVsbG8=

2. Case-insensitive in practice

Many systems accept uppercase or lowercase hex interchangeably, simplifying format conventions. Base64 is strictly case-sensitive.

3. Easy manual construction

Short data like color codes, MAC addresses, and memory addresses fits hex naturally:

# Colors are hex
#FFFFFF  # white
#FF0000  # red

# MAC addresses use hex with separators
01:23:45:67:89:ab

# Memory addresses
0x7ffeefbff5c8

4. The standard for hash digests

SHA-256, MD5, and similar hash outputs are almost universally displayed in hex:

# SHA-256 produces 32 bytes, shown as 64 hex characters
echo -n "hello" | shasum -a 256
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Base64 would be more compact (44 characters vs 64), but the convention is hex, and every tool expects hex.

Where Base64 Wins

1. Compactness

33% vs 100% overhead. The difference shows up clearly on larger payloads.

2. URL-safe variant available

Standard Base64 uses + and /, which need escaping in URLs. Base64URL substitutes - and _, dropping directly into URLs without escaping:

import base64

# Standard Base64 (URL-unfriendly)
standard = base64.b64encode(b"\xfb\xff")  # b'+/8='

# Base64URL (URL-safe)
urlsafe = base64.urlsafe_b64encode(b"\xfb\xff")  # b'-_8='

3. Embeds in text formats

JSON, XML, and HTML can't hold raw binary. Base64 packs binary data into a text field:

{
  "image": "data:image/png;base64,iVBORw0KGgo..."
}

4. Handles multibyte data gracefully

When hex-encoding a UTF-8 string, each byte is encoded separately, so the output obscures the original structure. Base64 encodes the whole byte stream, yielding a more compact result.

When To Use Hex

  • Hash digests: standard representation for SHA-256, MD5, HMAC outputs
  • Cryptographic keys and IVs: easy manual comparison during debugging
  • Binary protocol debugging: network protocols, file formats, serialized data
  • Short binary values: colors, MAC addresses, UUIDs, memory addresses
  • Blockchain and cryptocurrency: addresses, transaction hashes, block hashes

When To Use Base64

  • Data URIs: images and fonts embedded in HTML or CSS
  • JWT: all three segments are Base64URL
  • Email attachments: MIME uses Base64 for binary attachments
  • Large binary payloads: storage and bandwidth savings add up
  • Binary fields in JSON or XML: API responses that embed binary

Performance Notes

Encoding speed is comparable between the two and rarely the bottleneck. But Base64 decoding requires careful padding handling; malformed padding breaks decoding. Hex decoding is simpler, just map every two characters to one byte.

# Command-line encoding
echo -n "Hello" | xxd -p        # hex:    48656c6c6f
echo -n "Hello" | base64        # base64: SGVsbG8=

Do It In Your Browser

Whether you're decoding a Base64 field from an API response, converting an image to a data URI, or shifting a number between hex and decimal, you should not be sending data to an unfamiliar server.

The Base64 Decoder, Number Base Converter, and SHA-256 Hash Generator tools all run entirely in your browser. Your binary data, API responses, and configuration snippets never leave your machine. That matters most when you're encoding content with embedded credentials, user data, or proprietary source.

The short version: reach for hex when debugging, working with short data, or displaying hash digests. Reach for Base64 when embedding in text formats, handling large payloads, or working in URL contexts. Each has its strengths, and understanding the density-vs-readability tradeoff tells you which fits.


Advertisement