base64.b32encode / b32decode

Base32 survives case changes and avoids look-alike characters, which is why TOTP secrets and other human-typed codes use it. Python is strict by default: lowercase and missing padding are errors until you opt in.

base64 functionPython 2.4+ (b32hex: 3.10+)Live demo
Common call
base64.b32decode(secret.upper() + '=' * (-len(secret) % 8))
Returns
bytes — b'NBSWY3DP' / b'hello'
Replaces
Hand-rolled 5-bit packing
Watch out
Lowercase input needs casefold=True; padding is required
base64.b32decode(ss — Data to encode, or Base32 text to decode. The decoded length must be a multiple of 8, = included.type: bytes-like (encode) · bytes-like | ASCII str (decode) · required, casefoldcasefold — Decode only: accept lowercase letters (the input is uppercased first).type: bool · default: False=False, map01map01 — b32decode only: allow the digits 0 and 1 by mapping 0 to O and 1 to this letter ('L' or 'I'). Must be exactly 1 character.type: bytes | str · default: None=None)
→ bytes

Demo

Live evaluation
The same bytes in Base32 and in Base32hex.
Try:
Inputs
textstrany text
Code
import base64
data = 'hello'.encode()
(base64.b32encode(data), base64.b32hexencode(data))
Result
(b'NBSWY3DP', b'D1IMOR3F')

Base32 writes 5 bytes as 8 characters, so 2 bytes ("hi") need 4 characters plus 4 = signs. The decoder checks the length first: anything that is not a multiple of 8 — including unpadded input and input containing spaces — is "Incorrect padding" before a single character is looked at. Lowercase letters are "Non-base32 digit found" unless casefold=True, and so are 0, 1, 8 and 9, which the alphabet leaves out on purpose.

Parameters

NameTypeRequiredDescription
sbytes-like (encode) · bytes-like | ASCII str (decode)yesData to encode, or Base32 text to decode. The decoded length must be a multiple of 8, = included.
casefoldboolno (False)Decode only: accept lowercase letters (the input is uppercased first).
map01bytes | strno (None)b32decode only: allow the digits 0 and 1 by mapping 0 to O and 1 to this letter ('L' or 'I'). Must be exactly 1 character.

Return value

bytes — Encode: uppercase Base32 as ASCII bytes, padded with = to a multiple of 8. Decode: the original bytes.

Common patterns

Decode a TOTP / 2FA secret
Remove spaces, uppercase, restore padding.
import base64
def totp_key(secret):
    s = secret.replace(' ', '').upper()
    return base64.b32decode(s + '=' * (-len(s) % 8))
Generate a Base32 secret
20 random bytes give 32 characters with no padding.
import base64, secrets
secret = base64.b32encode(secrets.token_bytes(20)).decode('ascii')
Sortable IDs with Base32hex
The hex alphabet keeps the sort order of the underlying bytes.
import base64
key = base64.b32hexencode(raw_id).rstrip(b'=').decode('ascii')

Examples

1. Encode
import base64 base64.b32encode(b'hello')
Returns
b'NBSWY3DP'
2. Padding to 8 characters
import base64 base64.b32encode(b'hi')
Returns
b'NBUQ===='
3. Base32hex
import base64 base64.b32hexencode(b'hello')
Returns
b'D1IMOR3F'
4. Decode
import base64 base64.b32decode('NBSWY3DP')
Returns
b'hello'
5. Lowercase is rejected
import base64 base64.b32decode('nbswy3dp')
Returns
binascii.Error: Non-base32 digit found
6. casefold=True
import base64 base64.b32decode('nbswy3dp', casefold=True)
Returns
b'hello'
7. Decode Base32hex
import base64 base64.b32hexdecode('D1IMOR3F')
Returns
b'hello'
8. map01 for typed codes
import base64 base64.b32decode('MFRGG1Q=', map01='L') == base64.b32decode('MFRGGLQ=')
Returns
True

Pitfalls

1. Stripped padding
The length must be a multiple of 8; add the = signs back (-len(s) % 8 of them).
unpadded
import base64
base64.b32decode('MFRGG')
binascii.Error: Incorrect padding
padded
import base64
s = 'MFRGG'
base64.b32decode(s + '=' * (-len(s) % 8))
b'abc'
2. Lowercase secrets
Base32 is meant to be case-insensitive, but Python only accepts lowercase with casefold=True.
default
import base64
base64.b32decode('mfrgg===')
binascii.Error: Non-base32 digit found
casefold=True
import base64
base64.b32decode('mfrgg===', casefold=True)
b'abc'
3. Mixing up Base32 and Base32hex
Both alphabets have 32 characters but different values; the wrong decoder gives different bytes or an error.
b32decode
import base64
base64.b32decode('D1IMOR3F')
binascii.Error: Non-base32 digit found
b32hexdecode
import base64
base64.b32hexdecode('D1IMOR3F')
b'hello'

When to use

Use it
  • Codes people read aloud or type: TOTP secrets, license keys, backup codes
  • Case-insensitive file systems and DNS labels (Base32hex keeps sort order)
Reach for something else
  • Compact machine-to-machine data → Base64 (Base32 output is 20% longer)
  • Hex is wanted → b16encode or bytes.hex()

Notes

CPython impl
Pure Python in Lib/base64.py (5-byte quanta via int.from_bytes); b32hexencode / b32hexdecode were added in 3.10
Alphabets
Base32: A–Z 2–7. Base32hex: 0–9 A–V. Both pad with = to a multiple of 8 (RFC 4648 sections 6 and 7)
Exceptions
binascii.Error: "Incorrect padding" for a bad length or padding count, "Non-base32 digit found" for any other character

FAQ

Pass casefold=True: base64.b32decode(s, casefold=True). Without it, lowercase letters raise "Non-base32 digit found".