Two ways to write the same bytes
Computers handle bytes. Humans cannot read raw bytes (they include unprintable characters and characters that break URLs and JSON). So we encode them: turn binary data into a string of safe characters that can travel through HTTP, JSON, email, terminals.
Base64 and hex are the two encodings you actually meet in production. They do the same job, they just use different alphabets. Picking the right one is not a matter of taste, it changes the size of your output and the bugs you can hit on the way.
At a glance
| Feature | Hex (Base16) | Base64 |
|---|---|---|
| Output size vs binary | 2x larger | About 1.33x larger |
| Alphabet | 0-9 a-f | A-Z a-z 0-9 + / = |
| URL safe | Yes, by default | No, needs base64url variant |
| Common use | Hashes, fingerprints, addresses | Tokens, images in JSON, email |
| When each one wins | Human readable, copy paste safe | Smaller payloads, large blobs |
Hex: simple, readable, twice as long
Hex (also called Base16) writes each byte as two characters from the alphabet 0-9 a-f.
A 32 byte hash like SHA-256 becomes 64 characters:
deadbeef e29b41d4 a716446655440000 ...
The math is easy: every byte is two characters, no exceptions. A 1 KB blob becomes 2 KB of hex. A 100 MB file becomes 200 MB of hex string.
Hex is the right choice when:
- You want humans to read and compare values (hashes, fingerprints, addresses).
- You need stability across copy paste (no special characters that get mangled).
- The size penalty does not matter (small payloads, infrequent transmission).
Hex is the wrong choice when:
- You move large amounts of data (the 100 percent overhead is painful).
- You have a tight character budget (Twitter, SMS, certain headers).
Base64: compact, padded, almost URL safe
Base64 uses an alphabet of 64 characters (A-Z a-z 0-9 + /) and encodes three bytes as four characters.
The same 32 byte hash becomes 44 characters:
3q2+78Lp...
Roughly 33 percent overhead, much better than hexβs 100 percent. The catch is the alphabet. The + and / are normal characters in base64 but they are reserved in URLs (+ becomes a space, / separates path segments). Standard base64 in a URL parameter is a bug waiting to happen.
The fix is base64url, a variant that swaps + and / for - and _. Same size, no URL bugs. JWT, OAuth tokens, and most modern APIs use base64url specifically for this reason.
The padding character = at the end is sometimes there, sometimes not. Standard base64 always pads. base64url often skips the padding. Some parsers care, some do not. When you receive a base64 string, try both with and without padding before declaring it broken.
When to pick which one
Hashes you display to humans: hex. SHA-256 written as hex is recognisable across languages and tools. Base64 hashes look like noise.
Tokens that travel in URLs: base64url. Compact and URL safe.
JWT signatures: base64url, no choice (the spec says so).
Binary blobs in JSON: base64. JSON does not allow raw bytes, base64 keeps the size manageable.
File downloads with checksums: hex. The user wants to compare the displayed hash with the one in the file, character by character.
SMS that has to fit in 160 characters: base64url. Hex would double the cost.
When you need to convert between them or just inspect what you have, the base64 encoder on AldeaCode handles standard, URL safe, padded and unpadded variants. The hash generator outputs in either format. Everything runs in your browser, no upload.
The bugs that bite
A few things to watch:
Padding: a base64 string with the wrong number of = at the end fails to decode. If you receive a string and it ends in ==, the encoder added two padding characters because the input was not a multiple of three bytes. Stripping the padding is fine, modifying it is not.
Whitespace: some encoders insert line breaks every 76 characters (a holdover from email). Most decoders ignore them, some do not. If your string fails to decode, strip whitespace first.
Mixed alphabets: if a string has both + (standard base64) and - (base64url), it is corrupt. The two alphabets do not mix. Pick one and stick to it.
Hex case: hex is case insensitive in most parsers but conventionally written in lowercase. Some systems insist on lowercase, some on uppercase. When in doubt, lowercase.
The trailing newline: pasting a hash from a terminal often includes a trailing \n. Most decoders strip it, some do not. If your hash compares fail, check for invisible newlines.
A quick reference
// Hex
const buf = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
const hex = [...buf].map(b => b.toString(16).padStart(2, "0")).join("");
// "deadbeef"
// Base64 standard
const b64 = btoa(String.fromCharCode(...buf));
// "3q2+7w=="
// Base64url unpadded
const b64url = b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
// "3q2-7w"
For the reverse, atob() decodes standard base64 in the browser. For base64url, swap the characters back before calling atob().
The base64 encoder, the hash generator and the URL encoder on AldeaCode all run in your browser, no upload, no log. Two rules end most encoding bugs: pick base64url for anything URL adjacent, pick hex for anything humans will read.