Base32 decode to readable text
The Base32 decode tool reverses RFC 4648 Base32 encoding: each character is mapped back to 5 bits, the bits are regrouped into bytes, and the bytes are read as UTF-8 text. It is forgiving about formatting, which matters because Base32 strings are often typed by people or printed in groups.
Input normalisation
- Whitespace is removed, so grouped codes like
JBSW Y3DPwork. - Lowercase letters are converted to uppercase —
jbswy3dpdecodes the same asJBSWY3DP. - Missing
=padding is added automatically, so unpadded TOTP secrets and onion-style strings decode without editing. - Any character outside
A–Z,2–7and=is rejected with a clear message. The digits 0, 1, 8 and 9 are not valid in standard Base32.
Example
Input:
JBSWY3DPFQQEEYLTMUZTEII=Output:
Hello, Base32!And lowercase, unpadded: jbswy3dp → Hello.
Decoding binary values
Many Base32 strings do not contain text at all. A TOTP secret decodes to random bytes, so the output will look like gibberish or contain replacement characters (�). That is expected: the secret is a key, not a message. Never share TOTP secrets or paste them into tools you do not trust — anyone who has the secret can generate your one-time codes. This tool decodes in your browser and does not transmit the input, but the safest practice is to keep such secrets in your authenticator app only.
Base32 decoding in code
Python’s base64.b32decode(s, casefold=True) accepts lowercase input but still requires correct = padding, so unpadded secrets must be padded to a multiple of 8 characters first — a common stumbling block that this tool handles for you.
Troubleshooting
- “Input is not valid Base32”. Look for 0, 1, 8 or 9, which often come from misreading O, I, B or g, or from a different variant such as Base32hex or Crockford Base32.
- Output stops early. The string may have been truncated. Every complete byte is still decoded, and leftover bits at the end are dropped.
- It is actually Base64. Mixed-case strings with
+or/are Base64. Use the Base64 Decoder instead.
Encode text with Base32 Encode, or look at the raw bytes of a decoded value with String to Hex. Decoding runs in your browser.
How the bits line up
Every Base32 character stands for a number from 0 to 31, which is exactly 5 bits. Eight characters therefore carry 40 bits, or five whole bytes. Here is JBSWY3DP decoded by hand:
J=9 B=1 S=18 W=22 Y=24 3=27 D=3 P=15
01001 00001 10010 10110 11000 11011 00011 01111
→ 01001000 01100101 01101100 01101100 01101111
→ H e l l o
This also explains the padding rule. Input is processed in groups of 5 bytes, and a last group that is shorter leaves a fixed number of = signs:
| Bytes in last group | Characters | Padding |
|---|---|---|
| 1 | 2 | ====== |
| 2 | 4 | ==== |
| 3 | 5 | === |
| 4 | 7 | = |
So a string whose length leaves 1, 3 or 6 characters after the last full group of 8 cannot be valid Base32, which usually means a character was lost while copying. The tool then decodes the complete bytes that are present instead of rejecting the input.
Base32 decode in Python
base64.b32decode() from the standard library does the work and returns bytes, so call decode("utf-8") when the value is text. It is strict about its input: lowercase letters raise binascii.Error: Non-base32 digit found unless you pass casefold=True or uppercase the string, and missing = padding or spaces raise Incorrect padding. The helper below removes whitespace, uppercases and pads to a multiple of 8 before decoding.
import base64
def b32decode_text(value):
value = "".join(value.split()).upper()
value += "=" * (-len(value) % 8) # restore missing padding
return base64.b32decode(value).decode("utf-8")
print(b32decode_text("JBSWY3DPFQQEEYLTMUZTEII="))
print(b32decode_text("jbswy3dp"))
# raw bytes, for values that are not text (TOTP secrets, hashes)
print(base64.b32decode("JBSWY3DP").hex())
Both calls return the same text as the decoder above, which applies the same clean-up automatically; print .hex() instead of decoding when the value is a secret or other binary data.