Base32 & Base58
Alternative base encodings — when Base64 is the wrong tool.
Base32 & Base58
Base64 is the encoding everyone learns first. It's not the only one, and for some jobs it's the wrong tool. Two siblings dominate the spaces Base64 can't reach: Base32 when humans read the result aloud and Base58 when humans copy it visually.
Why "another" base encoding
All base-N encodings answer the same question: how do you stuff arbitrary bytes into a restricted alphabet? Base64 chose 64 characters because 6 bits packs cleanly into bytes (4 chars × 6 bits = 3 bytes). It works, but the alphabet includes + and / (URL-unsafe), 0/O and I/l (visually ambiguous), and the case-sensitive alphabet doubles the chance of human error when dictating a value.
Different alphabets fix different problems.
Base32 — case-insensitive, dictation-safe
RFC 4648 defines Base32 with the alphabet:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 2 3 4 5 6 7
Two design choices:
- Case-insensitive.
aandAdecode to the same value. You can dictate a Base32 string over a phone call without spelling case. - No
0,1,8,9. The digits that look like letters (zero/O, one/I/l) are removed. The remaining2–7are unambiguous.
Each Base32 character encodes exactly 5 bits (32 = 2⁵). Five bytes (40 bits) of input become 8 Base32 characters.
"hello" = 68 65 6C 6C 6F (5 bytes, 40 bits)
= NBSWY3DP (8 Base32 chars)
Where Base32 shows up
- TOTP / RFC 6238 secrets. When you scan a QR code for Authy or 1Password, the underlying secret is a Base32 string. That's why the manual-entry box always reads in capital letters and digits 2–7.
- Tor v3 onion addresses. 56 Base32 characters of public-key material →
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion. - DNSSEC NSEC3 records. The hashed owner names are encoded in
base32hex(the same alphabet idea, re-ordered). - GitHub two-factor recovery codes and many SSH key fingerprints.
The pattern: any value a human might re-type, dictate, or copy from a screenshot.
Base58 — visually unambiguous, no special characters
Base58 was popularised by Bitcoin. The alphabet:
123456789 ABCDEFGHJKLMNPQRSTUVWXYZ abcdefghijkmnopqrstuvwxyz
Note what's missing: 0, O (look identical in many fonts), I, l (also look identical), and every special character. What remains is exactly the characters that survive a hand-copy from paper, a phone screenshot, or a face-to-face dictation without ambiguity.
The trade-off: 58 isn't a power of 2. There's no neat "X bits per character". Encoding is done by treating the input as a big-endian integer, dividing by 58 repeatedly, and reading the remainders as alphabet indices. Each character carries log₂(58) ≈ 5.86 bits. It's slightly less efficient than Base32 (5 bits/char becomes 5.86 bits/char — more efficient actually) but slower because the algorithm is O(n²) in the input length.
[0x00, 0x01, 0x02, 0x03] => "11Ldp"
Where Base58 shows up
- Bitcoin addresses —
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNais Base58Check (Base58 with a checksum). - IPFS CIDv0 — the older "Qm..." style content identifiers are Base58 of a multihash.
- Solana keys — public/private keys in the Solana ecosystem.
- Flickr photo IDs, short URL slugs, anywhere a system wants short tokens that survive being copied through tweets and SMS.
When to use which
| Constraint | Use |
|---|---|
| Going over the wire, want size | Base64 (URL-safe variant for URLs) |
| Humans will read it aloud | Base32 |
| Humans will copy it visually | Base58 |
| Bytes are already hex-friendly | Hex |
Going inside a URL with + and = problems |
Base64 with URL-safe alphabet |
What to watch out for
- Base32 padding. The default alphabet pads with
=to a multiple of 8 chars. Some implementations drop padding (TOTP secrets typically do, Tor onion v3 addresses don't). Strip and re-add as needed. - Base58 has variants. The Bitcoin/IPFS alphabet (above) is the most common. Ripple uses a slightly different ordering. Always check which alphabet a library expects.
- Don't confuse Base32 and Base32hex. They have the same alphabet size but different orderings. Tor uses Base32; DNSSEC uses Base32hex.
- Base58Check is not just Base58. Bitcoin addresses include a 4-byte SHA-256 checksum at the end so a typo is caught before the funds vanish. If you're building an address-like identifier, bake in a checksum.
Worked example: a TOTP secret
# Generate a 160-bit secret, encode it as Base32 — the format every authenticator app expects.
$ openssl rand 20 | base32
NBSWY3DPEB3W64TMMQQGS4DBNZRXG2A=
# Strip the padding (standard for TOTP) and feed it to oathtool to test:
$ oathtool --totp -b NBSWY3DPEB3W64TMMQQGS4DBNZRXG2A
123456
That's the full lifecycle: random bytes → Base32 string → QR code → six-digit code on a phone. Every step assumes the secret is something a human can read, which is why Base32 wins this niche.
Tools in the wild
4 tools- libraryrfc4648 (npm)free tier
Spec-faithful Base16/32/32hex/64/64url encoder for Node and browsers.
- librarybs58 (npm)free tier
The canonical Base58 implementation used across the Bitcoin ecosystem.
- librarypython-base58free tier
Base58 / Base58Check for Python. Drop-in for Bitcoin / Solana / IPFS data.
- clioathtoolfree tier
CLI to generate / verify TOTP codes from Base32 secrets — handy for testing.