Seeded two-factor backup code generator
Ask for 5 recovery codes with a secret seed and you get 5 unique one-time codes such as 8476-8082, always the same list in the same order for that seed, so they can be rebuilt later. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Quantity
- 5
- Seed
- example-secret-seed
- Output format
- ####-####
- Codes
- 8476-8082, 9390-7155, 2863-8007, 5730-2714, 0935-9480
This seeded two-factor backup code generator creates an exact number of unique, one-time recovery codes in the familiar four-digits, hyphen, four-digits format. Supply a count and a secret seed, and the same inputs always produce the same ordered set. Generation is local computation only: it uses no network call, clock, random source, account data, or hidden state. Treat the seed and every resulting code as sensitive authentication material.
Choose the count and protect the seed
Start by choosing how many recovery codes the account or application should issue. The count must be an integer from one through one thousand; a request below one is rejected instead of silently producing an empty set. Then provide a non-empty seed.
The seed is the complete source of reproducibility, so identical seed and count values produce identical codes in identical order. Use a long, high-entropy secret created and stored through your organization’s approved secret-management process. A person’s name, email address, project label, or short memorable phrase is not an appropriate production seed because another person may guess it and reconstruct the codes.
Do not reuse one seed across users, environments, or services. Domain separation inside the algorithm prevents accidental overlap with unrelated derivations, but it cannot compensate for a weak or exposed seed. The seed is limited to 4,096 characters to keep execution bounded and predictable.
Understand the deterministic generation process
Each output position is derived independently with SHA-256 from a versioned label, the exact seed length, the seed text, the zero-based position, and a retry counter. Including the seed length makes the input framing unambiguous, while the versioned label separates this purpose from other seeded calculations. The generator reads unsigned values from the digest and uses rejection sampling before reducing them to eight decimal digits.
That rejection step avoids the small distribution bias that direct modulo conversion would introduce. It formats every accepted number as two groups of four digits separated by a hyphen, preserving leading zeroes. A set tracks values already emitted; if a collision occurs, the retry counter is advanced deterministically until a new code is found.
Therefore the result contains the requested number of unique codes, and rerunning the same request recreates the same collision decisions as well as the same final list. No network service, system clock, runtime random generator, persisted cache, or mutable module state can influence the answer. Changing even one seed character normally changes the entire list.
Deploy, store, and invalidate codes safely
Generation is only one part of a backup-code workflow. Your authentication service must store an approved verifier for each code, mark a code consumed after its first successful use, rate-limit attempts, and provide a deliberate revocation or regeneration action. Never log the seed or plain codes in analytics, traces, support tickets, build output, or shared chat.
Deliver codes to the authenticated user through a protected session and encourage offline storage in a password manager or another secure location. Determinism makes disaster recovery and controlled provisioning easier, but it also means anyone who obtains the seed can reconstruct every code. Rotate to a new independent seed whenever exposure is suspected, when a user requests regeneration, or when policy requires invalidating the old set.
The grouped digits improve transcription but provide only one hundred million possible values per code, so online defenses remain essential.
What you can do with it
Provision recovery codes
Create a controlled set of grouped-digit recovery codes during enrollment while keeping derivation reproducible from a protected per-user seed.
Reconstruct an approved code set
Recover the same ordered list during a documented incident process when the protected seed remains available but the display copy does not.
FAQ
What format do the codes use?
Every code contains eight decimal digits displayed as two four-digit groups, such as 0123-4567. Leading zeroes are retained.
Will the same inputs always return the same codes?
Yes. The algorithm has no random, clock, network, or stored-state dependency, so the same count and exact seed reproduce the same ordered list.
Are duplicate codes possible in one response?
No. Collisions are detected and deterministically retried until the requested list contains unique codes.
Can I use a memorable seed?
Not for production. Use a long, high-entropy secret from an approved secret-generation and storage process because possession or guessing of the seed permits reconstruction.
Can I get a fresh set of backup codes by changing only how many I ask for?
No. With the same seed, the codes in the shared positions stay identical, so asking for 6 instead of 5 repeats the first five. Use a new secret seed when you need a completely new list.
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/security/two-factor-backup-codes-generate \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"count":8,"seed":"correct-horse-battery-staple-2026"}'const res = await fetch("https://api.kit.forhosting.com/security/two-factor-backup-codes-generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"count": 8,
"seed": "correct-horse-battery-staple-2026"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/security/two-factor-backup-codes-generate",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"count": 8,
"seed": "correct-horse-battery-staple-2026"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/security/two-factor-backup-codes-generate", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"count":8,"seed":"correct-horse-battery-staple-2026"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"count":8,"seed":"correct-horse-battery-staple-2026"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/security/two-factor-backup-codes-generate", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"count": 8,
"seed": "correct-horse-battery-staple-2026"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "security.two_factor_backup_codes_generate",
"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
Deploy, store, and invalidate codes safely
Do not merely request a different count with the old seed: positions shared by both requests remain the same. The API costs $0.002 per request; the browser path uses the same pure computation.
Integration use cases
Build stable authentication tests
Generate fixed backup-code fixtures for integration tests without checking a hand-maintained list into the repository.
Technical questions
What happens when the count is zero or negative?
The request fails with an invalid-input error stating that count must be at least one.
How much does an API request cost?
Each API request costs $0.002. The browser version runs the same pure algorithm locally.