Disclaimer: This is the first (and for now only) post written with AI assistance (but based on internal knowledge base). It was modified, extended and cleaned up, but may still contain AI related hallucinations.
Excerpt: eduMFA (a fork of privacyIDEA) only accepts hex‑encoded TOTP secret keys; this post shows why, and how to convert between hex and Base32 with ready‑to‑use Python scripts.
Introduction
Two‑factor authentication (2FA) is now a baseline security requirement for schools, enterprises, and public services. eduMFA, the identity‑aware MFA solution built on privacyIDEA, enforces a strict input format for TOTP (Time‑Based One‑Time Password) secret keys: hex encoding. If you try to enroll a Base32 key – a format most authenticator apps display – you’ll see an enrollment error. This post explains the reasoning behind the hex requirement, walks through the pitfalls of mismatched encodings, and provides three tiny Python utilities that converts the discussed formats.
Why eduMFA Demands Hex‑Encoded Secrets
The upstream project privacyIDEA stores TOTP seed values as raw binary data, eduMFA does the same. When exporting or importing via the REST API, the binary blob is represented as a hex‑encoded string. This design choice eliminates ambiguities caused by Base32 padding (=) or line‑breaks that some apps inject.
“eduMFA / privacyIDEA expect hex encoded TOTP secret key (and rejects other formats)”
If you feed a Base32 string directly, the server cannot decode it to the underlying binary seed, and the enrollment call fails with a 400 error. Converting your secret to hex first guarantees compatibility across all API clients, CLI tools, and the web UI.
Converting Between Hex, Base32, and Base64
Below are three self‑contained Python 3 scripts that perform the needed transformations. They purposefully avoid external dependencies beyond the Python standard library, making them ideal for quick use on Linux, macOS, or Windows PowerShell.
| Script | Input | Output | Typical Use |
|---|---|---|---|
TOTP-b32tohex.py | Base32 secret | Hex string | When you have a QR‑generated Base32 secret and need the hex version for eduMFA |
TOTP-hex2b32.py | Hex secret | Base32 string | Base32 is used by KeepassXC for example. You may also need it for QR code generation. |
TOTP-converter.py | Hex secret | Base64 + Base32 + hex reconstructions | Full‑cycle sanity check |
1. Base32 → Hex (TOTP-b32tohex.py)
#!/usr/bin/env python3
import base64
import sys
# First argument is the Base32 string (no padding required)
b32 = sys.argv[1]
# Decode Base32 to raw bytes, then show hex representation
bin_out32 = base64.b32decode(b32)
print(f'{bin_out32.hex()}')
Run it like:
linux # ./TOTP-b32tohex.py MHNLU4ELS7XN44SNBGARKJBOFYBG25OW
61daba708b97eede724d098115242e2e026d75d6
The output hex string can be fed directly to eduMFA’s enrollment API.
2. Hex → Base32 (TOTP-hex2b32.py)
#!/usr/bin/env python3
import codecs
import base64
import sys
hex_in = sys.argv[1]
bin_in = codecs.decode(hex_in, 'hex')
b32 = base64.b32encode(bin_in).decode()
print(f'{b32}')
Example:
linux # ./TOTP-hex2b32.py 61daba708b97eede724d098115242e2e026d75d6 MHNLU4ELS7XN44SNBGARKJBOFYBG25OW
You can now generate a QR code for a user who prefers Base32‑based apps like Google Authenticator.
3. Full‑Cycle Converter (TOTP-converter.py)
#!/usr/bin/env python3
import codecs
import base64
import sys
hex_in = sys.argv[1] # Hex secret supplied by the admin
bin_in = codecs.decode(hex_in, 'hex')
# Encode to Base64 (standard RFC 4648)
b64 = base64.b64encode(bin_in).decode()
print(f'base64 = {b64}')
# Encode to Base32
b32 = base64.b32encode(bin_in).decode()
print(f'base32 = {b32}')
# Decode back to verify integrity
bin_out32 = base64.b32decode(b32)
print(f'bin (from base32) = {bin_out32}')
bin_out64 = base64.b64decode(b64)
print(f'bin (from base64) = {bin_out64}')
# Show hex again for confirmation
print(f'hex (from base32) = {bin_out32.hex()}')
print(f'hex (from base64) = {bin_out64.hex()}')
The script prints all representations and confirms that round‑tripping does not corrupt the secret. This is useful during migration projects or automated provisioning pipelines.
Generating QR codes
As QR codes were mentioned above, here’s how to create a QR code for your favorite TOTP app. Make sure to replace “username” and the second (!) “Issuer” with desired values:
linux # sudo apt install qrencode
linux # qrencode -t PNG -o totp_qr.png -s 10 "otpauth://totp/Issuer:username?secret=MHNLU4ELS7XN44SNBGARKJBOFYBG25OW&issuer=Issuer"
Here’s the resulting PNG image:

Troubleshooting Common Errors
| Symptom | Likely Cause | Fix |
|---|---|---|
Invalid secret format error from eduMFA | Secret contains non‑hex characters or padding (=) | Verify input with grep -E '^[0-9a-f]+$' or regenerate using the script |
| QR code shows garbled characters | Used Base64 instead of Base32 for the QR payload | Use TOTP-hex2b32.py to produce a proper Base32 string |
| Enrollment succeeds but login fails | Secret mismatch between server and authenticator | Re‑run TOTP-converter.py and compare the hex output from both sides |
