Base32 encoder & decoder
Encode text as RFC 4648 Base32, the A–Z and 2–7 alphabet behind authenticator secrets: Hello becomes JBSWY3DP. Decoding accepts lowercase and line breaks but rejects any stray symbol. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- Hello
- Calculation mode
- Encode
- Result
- JBSWY3DP
- Length
- 8
- Input bytes
- 5
- Alphabet
- RFC4648
Base32 turns arbitrary UTF-8 text into a compact string drawn only from the letters A through Z and the digits two through seven, optionally padded with equals signs so every block lands on an eight-character boundary. Unlike Base64, the alphabet avoids plus, slash, and other punctuation that breaks URLs, shell arguments, or case-folding file systems, which is why secret seeds, TOTP keys, and many protocol tokens still ship as Base32. This capability implements the RFC 4648 section six alphabet end to end: encode mode reads your plain string as UTF-8 bytes and emits a padded Base32 result; decode mode accepts mixed-case Base32, strips whitespace, validates every character, and returns the original UTF-8 text.
How RFC 4648 Base32 packs bits into A-Z and 2-7
RFC 4648 section six defines a thirty-two-symbol alphabet: the twenty-six uppercase Latin letters followed by the six digits two through seven. Encoding starts from raw bytes. For text, this capability first converts your string with UTF-8 so multi-byte characters stay portable across languages and platforms.
Those bytes are then read as a continuous bit stream. Every five bits select one alphabet index, producing one Base32 character. Five input bytes supply forty bits, which split cleanly into eight Base32 characters; shorter remainders leave a partial final quintet that is zero-padded on the right and then finished with equals-sign padding so the total length is always a multiple of eight.
Decoding reverses the map: each non-padding character contributes five bits, full bytes are emitted as soon as eight bits accumulate, and trailing padding is checked for legal placement. Whitespace is ignored so multi-line tokens pasted from email still work. Lowercase letters are folded to uppercase before lookup.
The standard this page implements is the classic RFC 4648 Base32 alphabet, not Base32hex and not Crockford Base32, which use different symbol tables and would break interop with TOTP and many legacy APIs.
Where Base32 encoding shows up in real work
Time-based one-time password secrets in RFC 6238 and authenticator apps almost always travel as Base32 so users can type them without confusing zero with the letter O or one with the letter I as badly as they would with Base64. DNS and some network configuration systems prefer Base32-like alphabets for opaque labels that must survive case folding. Mobile deep links and short offline tokens often choose Base32 because the symbol set is case-insensitive after normalization and avoids URL-reserved characters.
Support desks paste multi-line secrets from tickets and need whitespace tolerance without accepting garbage symbols that would produce a wrong key. Curriculum authors teaching encoding schemes want one page dedicated to Base32 rather than burying it under a generic Base64 landing, because search intent and alphabet rules differ. If you need Base64 with a URL-safe alphabet, percent-encoding for URLs, or multi-base integer conversion, treat those as separate search intents and landings rather than overloading this Base32 encoder page.
What you can do with it
TOTP and authenticator secret interchange
Encode newly generated shared secrets as RFC 4648 Base32 for authenticator apps, or decode pasted Base32 seeds during account recovery and import.
Case-safe tokens for configs and links
Produce Base32 labels that survive case folding and avoid + / punctuation when embedding opaque identifiers in DNS-like names or deep links.
FAQ
Which Base32 alphabet does this use?
RFC 4648 section six: uppercase A-Z and digits 2-7, with '=' padding to a multiple of eight characters. It is not Base32hex and not Crockford Base32.
Can I both encode and decode?
Yes. Set mode to encode (default) to turn plain UTF-8 text into Base32, or mode to decode to recover text from a Base32 string.
Is lowercase Base32 accepted when decoding?
Yes. Letters are uppercased before alphabet lookup. Whitespace is stripped so multi-line pastes still work.
What does it cost?
The browser tool is free and runs locally.
Why does my Base32 result end in equals signs?
They are padding, so the total length is always a multiple of eight. Five bytes fill exactly eight characters, as Hello does in JBSWY3DP, but a single letter f becomes MY followed by six equals signs.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/enc/base32 \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Hello"}'const res = await fetch("https://api.kit.forhosting.com/enc/base32", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "Hello"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/enc/base32",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "Hello"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/enc/base32", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"Hello"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"Hello"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/enc/base32", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "Hello"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "enc.base32",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
An illegal symbol during decode fails immediately with invalid_input so bad paste jobs never silently corrupt data.
How RFC 4648 Base32 packs bits into A-Z and 2-7
Any character outside A-Z, two through seven, and the padding equals sign raises an invalid_input error naming the position and the offending symbol. That strict rejection is intentional: silent replacement would hide transcription mistakes in authenticator secrets and wire formats. The entire path is deterministic floating-point-free integer work with no network, no random seeds, and no wall-clock timestamps, so identical inputs always produce identical JSON.
What you send and what you get back
Send a required string field named text and an optional mode field set to encode or decode. Encode mode treats text as plain UTF-8 content and returns a result string in the RFC 4648 alphabet with padding. Decode mode treats text as Base32, normalizes case and whitespace, validates the alphabet, and returns the recovered UTF-8 string in result. Both directions echo mode, the original text, result, a length field for the primary output string, alphabet and charset labels, a padding flag, and a short standard reference string so clients can assert which profile they hit. Classic checks include encoding Hello to JBSWY3DP, encoding the single letter f to MY with six padding equals signs, and decoding a known padded block back to foobar. When mode is omitted, encode is the default so a single payload still does useful work. Encode also reports input_bytes; decode reports output_bytes. Missing text, non-string objects, unsupported mode values, strings longer than the documented maximum, illegal Base32 characters, padding after a non-padding character, too many padding marks, or lengths that are not a multiple of eight all fail with invalid_input and a clear English message. Validation failures do not consume prepaid balance. Pricing for successful API calls is the published base of $0.002 per item.
Where Base32 encoding shows up in real work
Provisioning QR payloads and backup codes therefore need a reliable encode path when you generate keys and a strict decode path when you import them. Because this capability is a pure parse engine, API pricing is the published base of $0.002 per successful item and failed validation does not consume balance.
Integration use cases
Cross-language golden test vectors
Generate encode and decode fixtures that match Python, Go, and OpenSSL Base32 so multi-language CI shares one ground truth.
Technical questions
What happens if the Base32 string has an invalid character?
Decode fails with invalid_input, naming the position and the illegal character. Nothing is charged for validation failures.