The String Under Your QR Code

Every time you set up 2FA and tap "can't scan the QR code, enter it manually," a website hands you a string like JBSWY3DPEHPK3PXP. That string is not random gibberish, and it is not encrypted. It is your raw secret key, a plain sequence of bits, run through an encoding scheme called Base32, chosen specifically because it is safe for a human to read off a screen and type into a phone without making a mistake. Understanding exactly how that transformation works also explains why authenticator apps insist on this particular format instead of the Base64 you might already recognize from JWTs or email attachments.
The Base32 Character Set
Base32 encodes binary data using exactly 32 characters: the uppercase letters A through Z and the digits 2 through 7.
A=0 B=1 C=2 D=3 E=4 F=5 G=6 H=7
I=8 J=9 K=10 L=11 M=12 N=13 O=14 P=15
Q=16 R=17 S=18 T=19 U=20 V=21 W=22 X=23
Y=24 Z=25 2=26 3=27 4=28 5=29 6=30 7=31
Notice what is deliberately left out: 0, 1, 8, and 9. They are excluded because they look too much like letters already in the set, 0 next to O, 1 next to I or lowercase l, 8 next to B, in many fonts. Removing them means a person reading the string off a screen, or worse, off a printed backup sheet, cannot mistype it in a way that produces a different but still valid-looking secret.
Full Worked Example: Encoding "Hi" Step by Step
Base32 works by regrouping the input's bits into chunks of 5, since 2 to the power of 5 equals exactly 32, one chunk maps to exactly one output character. Here is the two-character string "Hi" encoded completely, one step at a time.
Step 1: Convert each character to its 8-bit binary (ASCII) form.
H = 72 = 01001000
i = 105 = 01101001
Step 2: Concatenate all the bits into one continuous stream.
01001000 01101001
(16 bits total)
Step 3: Re-group the 16-bit stream into chunks of 5 bits, left to right.
01001 00001 10100 1
(5) (5) (5) (1 leftover bit)
Step 4: Pad the final incomplete chunk with zeros on the right to make it a full 5 bits.
01001 00001 10100 10000
Step 5: Convert each 5-bit chunk to its decimal value, then look up the matching character.
01001 = 9 -> J
00001 = 1 -> B
10100 = 20 -> U
10000 = 16 -> Q
Step 6: Pad the output string itself to a multiple of 8 characters with the "=" symbol.
Two input bytes (16 bits) produce 4 real data characters, but Base32 output length is always padded up to the next multiple of 8 characters. Four real characters need four padding characters to reach 8:
Result: JBUQ====
Sixteen bits do not divide evenly into 5-bit groups (16 divided by 5 leaves a remainder), which is exactly why step 4's zero-padding and step 6's "=" padding both exist, one pads the bits so the last group is a full 5 bits, the other pads the output string to a full 8-character block, and these are two separate kinds of padding solving two separate problems.
Why Five Bytes Is the Magic Number
Five input bytes equal exactly 40 bits, and 40 divides evenly by 5, producing exactly 8 Base32 characters with no leftover bits and no padding needed at all. That is why Base32 output length is always calculated in blocks of 5 input bytes to 8 output characters. A 20-byte TOTP secret (the standard 160-bit secret size) divides evenly into four 5-byte blocks, producing exactly 32 Base32 characters with zero padding, which is exactly why a typical authenticator secret you type in by hand is 32 characters long with no trailing equals signs.
Base32 vs Base64 vs Hex: Choosing the Right Encoding
| Feature | Base32 | Base64 | Hexadecimal |
|---|---|---|---|
| Character set | A-Z, 2-7 (32 chars) | A-Z, a-z, 0-9, +, / (64 chars) | 0-9, a-f (16 chars) |
| Bits per character | 5 | 6 | 4 |
| Output size vs input | ~160% of input | ~133% of input | 200% of input |
| Case sensitive | No (uppercase only) | Yes | Usually not (case ignored) |
| URL safe as-is | Yes | No, standard Base64 needs escaping for + and / | Yes |
| Human typeable | Best, no ambiguous characters | Weaker, mixed case and symbols invite errors | Good, but twice as long as Base32 for the same data |
| Typical use | TOTP secrets, DNS records, onion addresses | Email attachments, JWTs, embedded images | Hash digests, color codes, memory addresses |
Hex is the least space-efficient of the three (4 bits per character means you need twice as many characters as bytes), but it is the most universally recognized, which is why tools like our Hash Generator output SHA-256 and MD5 digests in hex rather than Base32. Base64 packs the most data per character, which is why it dominates anywhere transmission size actually matters, like attaching a file to an email. Base32 sacrifices some efficiency specifically to buy human-readability and case-insensitivity, a trade that makes sense exactly in the narrow set of situations where a person might need to read or retype the string. If you frequently move between these formats, our Hex Converter and Base64 Encoder/Decoder handle the other two directly in your browser.
Why TOTP Specifically Requires Base32
TOTP secrets, defined in RFC 4226 and RFC 6238, standardize on Base32 for three concrete reasons:
- Manual entry has to survive human error. Not every setup flow supports scanning a QR code, some users need to type the secret by hand, and Base32's confusion-free alphabet minimizes transcription mistakes at the exact moment a mistake would silently produce a broken authenticator.
- No special characters to escape. Base32 strings drop cleanly into URLs (the
otpauth://scheme used inside QR codes), configuration files, and command-line tools without any need for URL-encoding or shell escaping. - The output stays a manageable length. A 20-byte (160-bit) secret, the standard TOTP secret size, becomes exactly 32 Base32 characters, long enough to be secure and short enough that a person can still type it correctly if they have to.
You can see this in action with our 2FA Secret Generator, which outputs properly formatted, padding-free Base32 secrets ready to paste into any authenticator app, and our live TOTP code generator, which takes a Base32 secret and produces the same rotating codes your phone would.
Other Places Base32 Shows Up
- Tor .onion addresses: version 3 onion service addresses are 56-character Base32 strings derived from an Ed25519 public key, chosen for the exact same case-insensitive, error-resistant reasons TOTP uses it.
- Bech32 and Bitcoin: Bech32, used for SegWit Bitcoin addresses (the ones starting with "bc1"), is a close relative of Base32 with an added checksum specifically designed to catch and even help correct common typos, since a mistyped cryptocurrency address can mean permanently lost funds.
- DNSSEC: NSEC3 records, part of DNSSEC's mechanism for proving a domain name does not exist, encode hashed owner names using a Base32 variant (specifically "Base32hex," a small alphabet reordering) for compact, comparison-friendly storage in DNS responses.
- Content-addressable storage: some file storage and backup systems use Base32 hashes as filenames, avoiding case-sensitivity headaches on filesystems like Windows' that treat "file.txt" and "FILE.TXT" as the same name.
Encode and Decode Base32 Instantly
Try the full encoding process yourself with our free Base32 Encoder/Decoder, running entirely client-side with nothing sent to a server. Pair it with our 2FA Secret Generator to produce a properly formatted Base32 TOTP secret ready for any authenticator app.
Frequently Asked Questions
Is Base32 the same as Base64?
No. They use different character sets and different bit groupings, 5 bits per character for Base32 versus 6 bits per character for Base64, which gives Base64 better space efficiency (about 33% overhead versus roughly 60% for Base32). Base32 is chosen instead of Base64 specifically when human readability and case-insensitivity matter more than compactness, which is exactly the situation with a manually typed TOTP secret.
Why does Base32 skip 0, 1, 8, and 9?
To prevent transcription errors when a person reads or types the encoded string. The digit 0 looks like the letter O, the digit 1 looks like the letter I or a lowercase l, and 8 can be confused with B in some typefaces. Restricting the digit set to 2 through 7 removes every one of these ambiguous pairs.
Is Base32 encoding the same thing as encryption?
No, and this is a common misunderstanding. Encoding is a fully reversible representation change with no key or secret involved anywhere in the process. Anyone who sees a Base32 string can decode it back to the original bytes instantly, with no password needed. For a TOTP secret specifically, all of the actual security comes from keeping that decoded value confidential, Base32 itself provides zero protection on its own.
What is Base32 padding, and do I actually need it?
Padding, the "=" character, pads the output string up to a multiple of 8 characters whenever the input length does not divide evenly into 5-byte blocks. Many real-world implementations, including most authenticator apps, accept Base32 strings with or without trailing padding. When you are implementing your own decoder, handle both cases to stay compatible with whatever a user pastes in.
Why is a typical TOTP secret exactly 32 characters long with no padding?
Because the standard TOTP secret size is 20 bytes (160 bits), and 20 bytes divides evenly into four 5-byte blocks. Each 5-byte block converts cleanly into 8 Base32 characters with no leftover bits, so four blocks produce exactly 32 characters and no padding is ever needed. That is not a coincidence, it is the reason 160 bits became the conventional TOTP secret size in the first place.